From 6a1dc8b5ae6b077d0e9adb7b6708cc3c1bb07fa8 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 30 Nov 2020 22:28:01 +0100 Subject: [PATCH 01/22] New test for NWItem class --- nw/core/item.py | 14 +- tests/conftest.py | 271 +++++++++++----------- tests/{test_item.py => test_core_item.py} | 190 +++++++++++---- 3 files changed, 300 insertions(+), 175 deletions(-) rename tests/{test_item.py => test_core_item.py} (65%) diff --git a/nw/core/item.py b/nw/core/item.py index 6793af39..ead16ab8 100644 --- a/nw/core/item.py +++ b/nw/core/item.py @@ -96,20 +96,21 @@ class NWItem(): return False if "handle" in xItem.attrib: - self.itemHandle = xItem.attrib["handle"] + self.setHandle(xItem.attrib["handle"]) else: logger.error("XML item entry does not have a handle") return False if "parent" in xItem.attrib: - self.itemParent = xItem.attrib["parent"] + self.setParent(xItem.attrib["parent"]) + + if "order" in xItem.attrib: + self.setOrder(xItem.attrib["order"]) retStatus = True for xValue in xItem: if xValue.tag == "name": self.setName(xValue.text) - elif xValue.tag == "order": - self.setOrder(xValue.text) elif xValue.tag == "type": self.setType(xValue.text) elif xValue.tag == "class": @@ -156,7 +157,10 @@ class NWItem(): def setName(self, theName): """Set the item name. """ - self.itemName = theName.strip() + if isinstance(theName, str): + self.itemName = theName.strip() + else: + self.itemName = "" return def setHandle(self, theHandle): diff --git a/tests/conftest.py b/tests/conftest.py index b8cc2fef..97aafd7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ import os from nwdummy import DummyMain -from PyQt5.QtWidgets import QMessageBox +# from PyQt5.QtWidgets import QMessageBox sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) @@ -20,7 +20,7 @@ from nw.config import Config # noqa: E402 ## @pytest.fixture(scope="session") -def nwTemp(): +def tmpDir(): """A temporary folder for the test session. This folder is presistent after the test so that the status of generated files can be checked. The folder is instead cleared before a new test session. @@ -33,170 +33,179 @@ def nwTemp(): os.mkdir(tempDir) return tempDir -@pytest.fixture(scope="session") -def nwRef(): - """The folder where all the reference files are stored for verifying - the results of tests. - """ - testDir = os.path.dirname(__file__) - refDir = os.path.join(testDir, "reference") - return refDir +# @pytest.fixture(scope="session") +# def nwRef(): +# """The folder where all the reference files are stored for verifying +# the results of tests. +# """ +# testDir = os.path.dirname(__file__) +# refDir = os.path.join(testDir, "reference") +# return refDir ## # novelWriter Objects ## +# @pytest.fixture(scope="session") +# def tmpConf(nwTemp): +# """Create a temporary novelWriter configuration object. +# """ +# theConf = Config() +# theConf.initConfig(nwTemp, nwTemp) +# theConf.setLastPath("") +# return theConf + @pytest.fixture(scope="session") -def tmpConf(nwTemp): +def tmpConf(tmpDir): """Create a temporary novelWriter configuration object. """ theConf = Config() - theConf.initConfig(nwTemp, nwTemp) + theConf.initConfig(tmpDir, tmpDir) theConf.setLastPath("") return theConf -@pytest.fixture(scope="session") -def nwConf(nwRef, nwTemp): - """Temporary novelWriter configuration used for the dummy instance - of novelWriter's main GUI. - """ - theConf = Config() - theConf.initConfig(nwRef, nwTemp) - return theConf +# @pytest.fixture(scope="session") +# def nwConf(nwRef, nwTemp): +# """Temporary novelWriter configuration used for the dummy instance +# of novelWriter's main GUI. +# """ +# theConf = Config() +# theConf.initConfig(nwRef, nwTemp) +# return theConf @pytest.fixture(scope="session") -def nwDummy(nwRef, nwTemp, nwConf): +def dummyGUI(tmpConf): """Create a dummy instance of novelWriter's main GUI class. """ theDummy = DummyMain() - theDummy.mainConf = nwConf + theDummy.mainConf = tmpConf return theDummy ## # Temporary Test Folders ## -@pytest.fixture(scope="session") -def nwTempProj(nwTemp): - """A temporary folder for project tests. - """ - projDir = os.path.join(nwTemp, "proj") - if not os.path.isdir(projDir): - os.mkdir(projDir) - return projDir +# @pytest.fixture(scope="session") +# def nwTempProj(nwTemp): +# """A temporary folder for project tests. +# """ +# projDir = os.path.join(nwTemp, "proj") +# if not os.path.isdir(projDir): +# os.mkdir(projDir) +# return projDir -@pytest.fixture(scope="session") -def nwTempGUI(nwTemp): - """A temporary folder for GUI tests. - """ - guiDir = os.path.join(nwTemp, "gui") - if not os.path.isdir(guiDir): - os.mkdir(guiDir) - return guiDir +# @pytest.fixture(scope="session") +# def nwTempGUI(nwTemp): +# """A temporary folder for GUI tests. +# """ +# guiDir = os.path.join(nwTemp, "gui") +# if not os.path.isdir(guiDir): +# os.mkdir(guiDir) +# return guiDir -@pytest.fixture(scope="session") -def nwTempBuild(nwTemp): - """A temporary folder for build tests. - """ - buildDir = os.path.join(nwTemp, "build") - if not os.path.isdir(buildDir): - os.mkdir(buildDir) - return buildDir +# @pytest.fixture(scope="session") +# def nwTempBuild(nwTemp): +# """A temporary folder for build tests. +# """ +# buildDir = os.path.join(nwTemp, "build") +# if not os.path.isdir(buildDir): +# os.mkdir(buildDir) +# return buildDir -@pytest.fixture(scope="function") -def nwFuncTemp(nwTemp): - """A temporary folder for a single test function. - """ - funcDir = os.path.join(nwTemp, "ftemp") - if os.path.isdir(funcDir): - shutil.rmtree(funcDir) - if not os.path.isdir(funcDir): - os.mkdir(funcDir) - yield funcDir - if os.path.isdir(funcDir): - shutil.rmtree(funcDir) - return +# @pytest.fixture(scope="function") +# def nwFuncTemp(nwTemp): +# """A temporary folder for a single test function. +# """ +# funcDir = os.path.join(nwTemp, "ftemp") +# if os.path.isdir(funcDir): +# shutil.rmtree(funcDir) +# if not os.path.isdir(funcDir): +# os.mkdir(funcDir) +# yield funcDir +# if os.path.isdir(funcDir): +# shutil.rmtree(funcDir) +# return ## # Temp Folders for Projects ## -@pytest.fixture(scope="function") -def nwMinimal(nwTemp): - """A minimal novelWriter example project. - """ - testDir = os.path.dirname(__file__) - minimalStore = os.path.join(testDir, "minimal") - minimalDir = os.path.join(nwTemp, "minimal") - if os.path.isdir(minimalDir): - shutil.rmtree(minimalDir) - shutil.copytree(minimalStore, minimalDir) - cacheDir = os.path.join(minimalDir, "cache") - if os.path.isdir(cacheDir): - shutil.rmtree(cacheDir) - metaDir = os.path.join(minimalDir, "meta") - if os.path.isdir(metaDir): - shutil.rmtree(metaDir) - yield minimalDir - if os.path.isdir(minimalDir): - shutil.rmtree(minimalDir) - return +# @pytest.fixture(scope="function") +# def nwMinimal(nwTemp): +# """A minimal novelWriter example project. +# """ +# testDir = os.path.dirname(__file__) +# minimalStore = os.path.join(testDir, "minimal") +# minimalDir = os.path.join(nwTemp, "minimal") +# if os.path.isdir(minimalDir): +# shutil.rmtree(minimalDir) +# shutil.copytree(minimalStore, minimalDir) +# cacheDir = os.path.join(minimalDir, "cache") +# if os.path.isdir(cacheDir): +# shutil.rmtree(cacheDir) +# metaDir = os.path.join(minimalDir, "meta") +# if os.path.isdir(metaDir): +# shutil.rmtree(metaDir) +# yield minimalDir +# if os.path.isdir(minimalDir): +# shutil.rmtree(minimalDir) +# return -@pytest.fixture(scope="function") -def nwLipsum(nwTemp): - """A medium sized novelWriter example project with a lot of Lorem - Ipsum dummy text. - """ - testDir = os.path.dirname(__file__) - lipsumStore = os.path.join(testDir, "lipsum") - lipsumDir = os.path.join(nwTemp, "lipsum") - if os.path.isdir(lipsumDir): - shutil.rmtree(lipsumDir) - shutil.copytree(lipsumStore, lipsumDir) - cacheDir = os.path.join(lipsumDir, "cache") - if os.path.isdir(cacheDir): - shutil.rmtree(cacheDir) - metaDir = os.path.join(lipsumDir, "meta") - if os.path.isdir(metaDir): - shutil.rmtree(metaDir) - yield lipsumDir - if os.path.isdir(lipsumDir): - shutil.rmtree(lipsumDir) - return +# @pytest.fixture(scope="function") +# def nwLipsum(nwTemp): +# """A medium sized novelWriter example project with a lot of Lorem +# Ipsum dummy text. +# """ +# testDir = os.path.dirname(__file__) +# lipsumStore = os.path.join(testDir, "lipsum") +# lipsumDir = os.path.join(nwTemp, "lipsum") +# if os.path.isdir(lipsumDir): +# shutil.rmtree(lipsumDir) +# shutil.copytree(lipsumStore, lipsumDir) +# cacheDir = os.path.join(lipsumDir, "cache") +# if os.path.isdir(cacheDir): +# shutil.rmtree(cacheDir) +# metaDir = os.path.join(lipsumDir, "meta") +# if os.path.isdir(metaDir): +# shutil.rmtree(metaDir) +# yield lipsumDir +# if os.path.isdir(lipsumDir): +# shutil.rmtree(lipsumDir) +# return -@pytest.fixture(scope="function") -def nwOldProj(nwTemp): - """A minimal movelWriter project using the old folder structure. - """ - testDir = os.path.dirname(__file__) - oldProjStore = os.path.join(testDir, "oldproj") - oldProjDir = os.path.join(nwTemp, "oldproj") - if os.path.isdir(oldProjDir): - shutil.rmtree(oldProjDir) - shutil.copytree(oldProjStore, oldProjDir) - yield oldProjDir - if os.path.isdir(oldProjDir): - shutil.rmtree(oldProjDir) - return +# @pytest.fixture(scope="function") +# def nwOldProj(nwTemp): +# """A minimal movelWriter project using the old folder structure. +# """ +# testDir = os.path.dirname(__file__) +# oldProjStore = os.path.join(testDir, "oldproj") +# oldProjDir = os.path.join(nwTemp, "oldproj") +# if os.path.isdir(oldProjDir): +# shutil.rmtree(oldProjDir) +# shutil.copytree(oldProjStore, oldProjDir) +# yield oldProjDir +# if os.path.isdir(oldProjDir): +# shutil.rmtree(oldProjDir) +# return ## # Monkey Patch Dialogs ## -@pytest.fixture(scope="function") -def yesToAll(monkeypatch): - """Make the message boxes/questions always say yes. - """ - monkeypatch.setattr( - QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes - ) - monkeypatch.setattr( - QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes - ) - monkeypatch.setattr( - QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes - ) - monkeypatch.setattr( - QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes - ) - return +# @pytest.fixture(scope="function") +# def yesToAll(monkeypatch): +# """Make the message boxes/questions always say yes. +# """ +# monkeypatch.setattr( +# QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes +# ) +# monkeypatch.setattr( +# QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes +# ) +# monkeypatch.setattr( +# QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes +# ) +# monkeypatch.setattr( +# QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes +# ) +# return diff --git a/tests/test_item.py b/tests/test_core_item.py similarity index 65% rename from tests/test_item.py rename to tests/test_core_item.py index 95aa8470..a144b898 100644 --- a/tests/test_item.py +++ b/tests/test_core_item.py @@ -9,10 +9,11 @@ from lxml import etree from nw.core.project import NWProject, NWItem from nw.constants import nwItemClass, nwItemType, nwItemLayout -@pytest.mark.project -def testItemSettersSimple(nwDummy): - - theProject = NWProject(nwDummy) +@pytest.mark.core +def testCoreItemSetters(dummyGUI): + """Test all the simple setter classes for the NWItem class. + """ + theProject = NWProject(dummyGUI) theItem = NWItem(theProject) # Name @@ -20,12 +21,16 @@ def testItemSettersSimple(nwDummy): assert theItem.itemName == "A Name" theItem.setName("\t A Name ") assert theItem.itemName == "A Name" + theItem.setName(123) + assert theItem.itemName == "" # Handle theItem.setHandle(123) assert theItem.itemHandle is None theItem.setHandle("0123456789abcdef") assert theItem.itemHandle is None + theItem.setHandle("0123456789abg") + assert theItem.itemHandle is None theItem.setHandle("0123456789abc") assert theItem.itemHandle == "0123456789abc" @@ -36,6 +41,8 @@ def testItemSettersSimple(nwDummy): assert theItem.itemParent is None theItem.setParent("0123456789abcdef") assert theItem.itemParent is None + theItem.setParent("0123456789abg") + assert theItem.itemParent is None theItem.setParent("0123456789abc") assert theItem.itemParent == "0123456789abc" @@ -59,6 +66,19 @@ def testItemSettersSimple(nwDummy): theItem.setStatus("Main") assert theItem.itemStatus == "Main" + # Importance + theItem.itemClass = nwItemClass.NOVEL + theItem.setStatus("Nonsense") + assert theItem.itemStatus == "New" + theItem.setStatus("New") + assert theItem.itemStatus == "New" + theItem.setStatus("Note") + assert theItem.itemStatus == "Note" + theItem.setStatus("Draft") + assert theItem.itemStatus == "Draft" + theItem.setStatus("Finished") + assert theItem.itemStatus == "Finished" + # Expanded theItem.setExpanded(8) assert not theItem.isExpanded @@ -73,6 +93,20 @@ def testItemSettersSimple(nwDummy): theItem.setExpanded(True) assert theItem.isExpanded + # Exported + theItem.setExported(8) + assert not theItem.isExported + theItem.setExported(None) + assert not theItem.isExported + theItem.setExported("None") + assert not theItem.isExported + theItem.setExported("What?") + assert not theItem.isExported + theItem.setExported("True") + assert theItem.isExported + theItem.setExported(True) + assert theItem.isExported + # CharCount theItem.setCharCount(None) assert theItem.charCount == 0 @@ -105,10 +139,47 @@ def testItemSettersSimple(nwDummy): theItem.setCursorPos(1) assert theItem.cursorPos == 1 -@pytest.mark.project -def testItemClassSetter(nwDummy): + # Initial Count + theItem.setWordCount(234) + theItem.saveInitialCount() + assert theItem.initCount == 234 - theProject = NWProject(nwDummy) +# END Test testCoreItemSetters + +@pytest.mark.core +def testCoreItemTypeSetter(dummyGUI): + """Test the setter for all the nwItemType values for the NWItem + class. + """ + theProject = NWProject(dummyGUI) + theItem = NWItem(theProject) + + # Type + theItem.setType(None) + assert theItem.itemType == nwItemType.NO_TYPE + theItem.setType("NONSENSE") + assert theItem.itemType == nwItemType.NO_TYPE + theItem.setType("NO_TYPE") + assert theItem.itemType == nwItemType.NO_TYPE + theItem.setType("ROOT") + assert theItem.itemType == nwItemType.ROOT + theItem.setType("FOLDER") + assert theItem.itemType == nwItemType.FOLDER + theItem.setType("FILE") + assert theItem.itemType == nwItemType.FILE + theItem.setType("TRASH") + assert theItem.itemType == nwItemType.TRASH + theItem.setType(nwItemType.ROOT) + assert theItem.itemType == nwItemType.ROOT + +# END Test testCoreItemTypeSetter + +@pytest.mark.core +def testCoreItemClassSetter(dummyGUI): + """Test the setter for all the nwItemClass values for the NWItem + class. + """ + theProject = NWProject(dummyGUI) theItem = NWItem(theProject) # Class @@ -138,33 +209,17 @@ def testItemClassSetter(nwDummy): assert theItem.itemClass == nwItemClass.ARCHIVE theItem.setClass("TRASH") assert theItem.itemClass == nwItemClass.TRASH + theItem.setClass(nwItemClass.NOVEL) + assert theItem.itemClass == nwItemClass.NOVEL -@pytest.mark.project -def testItemTypeSetter(nwDummy): +# END Test testCoreItemClassSetter - theProject = NWProject(nwDummy) - theItem = NWItem(theProject) - - # Type - theItem.setType(None) - assert theItem.itemType == nwItemType.NO_TYPE - theItem.setType("NONSENSE") - assert theItem.itemType == nwItemType.NO_TYPE - theItem.setType("NO_TYPE") - assert theItem.itemType == nwItemType.NO_TYPE - theItem.setType("ROOT") - assert theItem.itemType == nwItemType.ROOT - theItem.setType("FOLDER") - assert theItem.itemType == nwItemType.FOLDER - theItem.setType("FILE") - assert theItem.itemType == nwItemType.FILE - theItem.setType("TRASH") - assert theItem.itemType == nwItemType.TRASH - -@pytest.mark.project -def testItemLayoutSetter(nwDummy): - - theProject = NWProject(nwDummy) +@pytest.mark.core +def testCoreItemLayoutSetter(dummyGUI): + """Test the setter for all the nwItemLayout values for the NWItem + class. + """ + theProject = NWProject(dummyGUI) theItem = NWItem(theProject) # Layout @@ -190,14 +245,22 @@ def testItemLayoutSetter(nwDummy): assert theItem.itemLayout == nwItemLayout.SCENE theItem.setLayout("NOTE") assert theItem.itemLayout == nwItemLayout.NOTE + theItem.setLayout(nwItemLayout.NOTE) + assert theItem.itemLayout == nwItemLayout.NOTE -@pytest.mark.project -def testItemXMLPackUnpack(nwDummy): +# END Test testCoreItemLayoutSetter - theProject = NWProject(nwDummy) - theItem = NWItem(theProject) +@pytest.mark.core +def testCoreItemXMLPackUnpack(dummyGUI): + """Test packing and unpacking XML objects for the NWItem class. + """ + theProject = NWProject(dummyGUI) nwXML = etree.Element("novelWriterXML") + # File + # ==== + + theItem = NWItem(theProject) theItem.setHandle("0123456789abc") theItem.setParent("0123456789abc") theItem.setOrder(1) @@ -206,7 +269,7 @@ def testItemXMLPackUnpack(nwDummy): theItem.setType("FILE") theItem.setStatus("Main") theItem.setLayout("NOTE") - theItem.setExpanded(True) + theItem.setExported(False) theItem.setParaCount(3) theItem.setWordCount(5) theItem.setCharCount(7) @@ -219,17 +282,18 @@ def testItemXMLPackUnpack(nwDummy): b"" b"" b"A NameFILENOVELNew" - b"TrueNOTE7" + b"FalseNOTE7" b"5311" b"" ) # Unpack + theItem = NWItem(theProject) assert theItem.unpackXML(xContent[0]) assert theItem.itemHandle == "0123456789abc" assert theItem.itemParent == "0123456789abc" assert theItem.itemOrder == 1 - assert theItem.isExpanded + assert theItem.isExported is False assert theItem.paraCount == 3 assert theItem.wordCount == 5 assert theItem.charCount == 7 @@ -238,6 +302,52 @@ def testItemXMLPackUnpack(nwDummy): assert theItem.itemType == nwItemType.FILE assert theItem.itemLayout == nwItemLayout.NOTE + # Folder + # ====== + + theItem = NWItem(theProject) + theItem.setHandle("0123456789abc") + theItem.setParent("0123456789abc") + theItem.setOrder(1) + theItem.setName("A Name") + theItem.setClass("NOVEL") + theItem.setType("FOLDER") + theItem.setStatus("Main") + theItem.setLayout("NOTE") + theItem.setExpanded(True) + theItem.setExported(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"" + b"A NameFOLDERNOVELNew" + b"True" + b"" + ) + + # Unpack + theItem = NWItem(theProject) + assert theItem.unpackXML(xContent[0]) + assert theItem.itemHandle == "0123456789abc" + assert theItem.itemParent == "0123456789abc" + assert theItem.itemOrder == 1 + assert theItem.isExpanded is True + assert theItem.isExported 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 + # Errors ## Not an Item @@ -268,3 +378,5 @@ def testItemXMLPackUnpack(nwDummy): assert etree.tostring(xDummy, pretty_print=False, encoding="utf-8") == ( b"" ) + +# END Test testCoreItemXMLPackUnpack From dd943116afab11f8ec81090324aefade9930cc70 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 30 Nov 2020 22:46:55 +0100 Subject: [PATCH 02/22] New test for core tools functions --- nw/core/tools.py | 20 ++++-- tests/test_core_item.py | 22 +++---- tests/{test_tools.py => test_core_tools.py} | 71 ++++++++++++--------- 3 files changed, 68 insertions(+), 45 deletions(-) rename tests/{test_tools.py => test_core_tools.py} (85%) diff --git a/nw/core/tools.py b/nw/core/tools.py index ef9fbc6b..342f62bb 100644 --- a/nw/core/tools.py +++ b/nw/core/tools.py @@ -141,6 +141,15 @@ def _numberToWordEN(numVal): tenWord = "" hunWord = "" + if not isinstance(numVal, int): + return "[NaN]" + + if numVal < 0: + return "[Negative]" + + if numVal > 999: + return "[Out of Range]" + if numVal == 0: return "Zero" @@ -166,19 +175,20 @@ def _numberToWordEN(numVal): 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine", } + retVale = "" hunWord = theHundreds.get(hunVal, "") if tenVal == 10: oneWord = theTeens.get(oneVal, "") - return f"{hunWord} {oneWord}".strip() + retVale = f"{hunWord} {oneWord}".strip() else: oneWord = theOnes.get(oneVal, "") if tenVal == 0: - return f"{hunWord} {oneWord}".strip() + retVale = f"{hunWord} {oneWord}".strip() else: tenWord = theTens.get(tenVal, "") if oneVal == 0: - return f"{hunWord} {tenWord}".strip() + retVale = f"{hunWord} {tenWord}".strip() else: - return f"{hunWord} {tenWord}-{oneWord}".strip() + retVale = f"{hunWord} {tenWord}-{oneWord}".strip() - return "" + return retVale diff --git a/tests/test_core_item.py b/tests/test_core_item.py index a144b898..e1457792 100644 --- a/tests/test_core_item.py +++ b/tests/test_core_item.py @@ -10,8 +10,8 @@ from nw.core.project import NWProject, NWItem from nw.constants import nwItemClass, nwItemType, nwItemLayout @pytest.mark.core -def testCoreItemSetters(dummyGUI): - """Test all the simple setter classes for the NWItem class. +def testCoreItem_Setters(dummyGUI): + """Test all the simple setters for the NWItem class. """ theProject = NWProject(dummyGUI) theItem = NWItem(theProject) @@ -144,10 +144,10 @@ def testCoreItemSetters(dummyGUI): theItem.saveInitialCount() assert theItem.initCount == 234 -# END Test testCoreItemSetters +# END Test testCoreItem_Setters @pytest.mark.core -def testCoreItemTypeSetter(dummyGUI): +def testCoreItem_TypeSetter(dummyGUI): """Test the setter for all the nwItemType values for the NWItem class. """ @@ -172,10 +172,10 @@ def testCoreItemTypeSetter(dummyGUI): theItem.setType(nwItemType.ROOT) assert theItem.itemType == nwItemType.ROOT -# END Test testCoreItemTypeSetter +# END Test testCoreItem_TypeSetter @pytest.mark.core -def testCoreItemClassSetter(dummyGUI): +def testCoreItem_ClassSetter(dummyGUI): """Test the setter for all the nwItemClass values for the NWItem class. """ @@ -212,10 +212,10 @@ def testCoreItemClassSetter(dummyGUI): theItem.setClass(nwItemClass.NOVEL) assert theItem.itemClass == nwItemClass.NOVEL -# END Test testCoreItemClassSetter +# END Test testCoreItem_ClassSetter @pytest.mark.core -def testCoreItemLayoutSetter(dummyGUI): +def testCoreItem_LayoutSetter(dummyGUI): """Test the setter for all the nwItemLayout values for the NWItem class. """ @@ -248,10 +248,10 @@ def testCoreItemLayoutSetter(dummyGUI): theItem.setLayout(nwItemLayout.NOTE) assert theItem.itemLayout == nwItemLayout.NOTE -# END Test testCoreItemLayoutSetter +# END Test testCoreItem_LayoutSetter @pytest.mark.core -def testCoreItemXMLPackUnpack(dummyGUI): +def testCoreItem_XMLPackUnpack(dummyGUI): """Test packing and unpacking XML objects for the NWItem class. """ theProject = NWProject(dummyGUI) @@ -379,4 +379,4 @@ def testCoreItemXMLPackUnpack(dummyGUI): b"" ) -# END Test testCoreItemXMLPackUnpack +# END Test testCoreItem_XMLPackUnpack diff --git a/tests/test_tools.py b/tests/test_core_tools.py similarity index 85% rename from tests/test_tools.py rename to tests/test_core_tools.py index 4832aadb..ceb146a4 100644 --- a/tests/test_tools.py +++ b/tests/test_core_tools.py @@ -7,7 +7,7 @@ import pytest from nw.core.tools import countWords, numberToRoman, numberToWord @pytest.mark.core -def testCountWords(): +def testCoreTools_CountWords(): """Test the word counter and the exclusion filers. """ testText = ( @@ -26,15 +26,46 @@ def testCountWords(): "\n" "\n" "The third paragraph.\n" + "\n" + "Dashes\u2013and even longer\u2014dashes." ) cC, wC, pC = countWords(testText) - assert cC == 108 - assert wC == 17 - assert pC == 3 + assert cC == 138 + assert wC == 22 + assert pC == 4 + +# END Test testCoreTools_CountWords @pytest.mark.core -def testNumberWords(): +def testCoreTools_RomanNumbers(): + """Test conversion of integers to Roman numbers. + """ + assert numberToRoman(None, False) == "NAN" + assert numberToRoman(0, False) == "OOR" + assert numberToRoman(1, False) == "I" + assert numberToRoman(2, False) == "II" + assert numberToRoman(3, False) == "III" + assert numberToRoman(4, False) == "IV" + assert numberToRoman(5, False) == "V" + assert numberToRoman(6, False) == "VI" + assert numberToRoman(7, False) == "VII" + assert numberToRoman(8, False) == "VIII" + assert numberToRoman(9, False) == "IX" + assert numberToRoman(10, False) == "X" + assert numberToRoman(14, False) == "XIV" + assert numberToRoman(42, False) == "XLII" + assert numberToRoman(99, False) == "XCIX" + assert numberToRoman(142, False) == "CXLII" + assert numberToRoman(542, False) == "DXLII" + assert numberToRoman(999, False) == "CMXCIX" + assert numberToRoman(2010, False) == "MMX" + assert numberToRoman(999, True) == "cmxcix" + +# END Test testCoreTools_RomanNumbers + +@pytest.mark.core +def testCoreTools_NumberWords(): """Test the conversion of integer to English words. """ assert numberToWord(0, "en") == "Zero" @@ -70,27 +101,9 @@ def testNumberWords(): assert numberToWord(2, "foo") == "Two" assert numberToWord(3, "foo") == "Three" -@pytest.mark.core -def testRomanNumbers(): - """Test conversion of integers to Roman numbers. - """ - assert numberToRoman(None, False) == "NAN" - assert numberToRoman(0, False) == "OOR" - assert numberToRoman(1, False) == "I" - assert numberToRoman(2, False) == "II" - assert numberToRoman(3, False) == "III" - assert numberToRoman(4, False) == "IV" - assert numberToRoman(5, False) == "V" - assert numberToRoman(6, False) == "VI" - assert numberToRoman(7, False) == "VII" - assert numberToRoman(8, False) == "VIII" - assert numberToRoman(9, False) == "IX" - assert numberToRoman(10, False) == "X" - assert numberToRoman(14, False) == "XIV" - assert numberToRoman(42, False) == "XLII" - assert numberToRoman(99, False) == "XCIX" - assert numberToRoman(142, False) == "CXLII" - assert numberToRoman(542, False) == "DXLII" - assert numberToRoman(999, False) == "CMXCIX" - assert numberToRoman(2010, False) == "MMX" - assert numberToRoman(999, True) == "cmxcix" + # Test out of range values + assert numberToWord(12345, "en") == "[Out of Range]" + assert numberToWord(-2345, "en") == "[Negative]" + assert numberToWord("234", "en") == "[NaN]" + +# END Test testCoreTools_NumberWords From 2a642ea40551c7677c2b2977161ab4966b6a7284 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 01:15:06 +0100 Subject: [PATCH 03/22] New test for NWTree class --- nw/core/tree.py | 21 +- tests/test_core_tree.py | 437 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 452 insertions(+), 6 deletions(-) create mode 100644 tests/test_core_tree.py diff --git a/nw/core/tree.py b/nw/core/tree.py index ac3ab45c..b76b6678 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -48,8 +48,10 @@ class NWTree(): self._treeOrder = [] # The order of the tree items on the tree view self._treeRoots = [] # The root items of the tree self._trashRoot = None # The handle of the trash root folder + self._archRoot = None # The handle of the archive root folder self._theIndex = 0 # The current iterator index self._treeChanged = False # True if tree structure has changed + self._handleSeed = None # Used for generating handles for testing return @@ -83,14 +85,15 @@ class NWTree(): if tHandle is None: tHandle = self._makeHandle() + if tHandle in self._projTree: + logger.warning("Duplicate handle %s detected, generating new" % tHandle) + tHandle = self._makeHandle() + logger.verbose("Adding item %s with parent %s" % (str(tHandle), str(pHandle))) nwItem.setHandle(tHandle) nwItem.setParent(pHandle) - self._projTree[tHandle] = nwItem - self._treeOrder.append(tHandle) - if nwItem.itemType == nwItemType.ROOT: logger.verbose("Item %s is a root item" % str(tHandle)) self._treeRoots.append(tHandle) @@ -104,7 +107,10 @@ class NWTree(): self._trashRoot = tHandle else: logger.error("Only one trash folder allowed") + return + self._projTree[tHandle] = nwItem + self._treeOrder.append(tHandle) self._setTreeChanged(True) return @@ -176,8 +182,9 @@ class NWTree(): except Exception as e: logger.error(str(e)) + return False - return + return True def sumWords(self): """Loops over all entries and adds up the word counts. @@ -244,6 +251,8 @@ class NWTree(): return True for aRoot in self._treeRoots: tItem = self.__getitem__(aRoot) + if tItem is None: + continue if theClass == tItem.itemClass: return False return True @@ -397,7 +406,7 @@ class NWTree(): del self._projTree[tHandle] else: logger.warning("Failed to delete item %s: item not found" % tHandle) - return False + return if tHandle in self._treeRoots: self._treeRoots.remove(tHandle) @@ -408,7 +417,7 @@ class NWTree(): self._setTreeChanged(True) - return True + return def __contains__(self, tHandle): """Checks if a handle exists in the tree. diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py new file mode 100644 index 00000000..1eff405d --- /dev/null +++ b/tests/test_core_tree.py @@ -0,0 +1,437 @@ +# -*- coding: utf-8 -*- +"""novelWriter NWTree Class Tester +""" + +import os +import pytest + +from lxml import etree + +from nw.core.project import NWProject, NWItem, NWTree +from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles + +@pytest.fixture(scope="session") +def dummyItems(dummyGUI): + """Create a list of dummy items. + """ + theProject = NWProject(dummyGUI) + + itemA = NWItem(theProject) + itemA.itemName = "Novel" + itemA.itemType = nwItemType.ROOT + itemA.itemClass = nwItemClass.NOVEL + itemA.isExpanded = True + + itemB = NWItem(theProject) + itemB.itemName = "Act One" + itemB.itemType = nwItemType.FOLDER + itemB.itemClass = nwItemClass.NOVEL + itemB.isExpanded = True + + itemC = NWItem(theProject) + itemC.itemName = "Chapter One" + itemC.itemType = nwItemType.FILE + itemC.itemClass = nwItemClass.NOVEL + itemC.itemLayout = nwItemLayout.CHAPTER + itemC.charCount = 300 + itemC.wordCount = 50 + itemC.paraCount = 2 + + itemD = NWItem(theProject) + itemD.itemName = "Scene One" + itemD.itemType = nwItemType.FILE + itemD.itemClass = nwItemClass.NOVEL + itemD.itemLayout = nwItemLayout.SCENE + itemD.charCount = 3000 + itemD.wordCount = 500 + itemD.paraCount = 20 + + itemE = NWItem(theProject) + itemE.itemName = "Outtakes" + itemE.itemType = nwItemType.ROOT + itemE.itemClass = nwItemClass.ARCHIVE + itemE.isExpanded = False + + itemF = NWItem(theProject) + itemF.itemName = "Trash" + itemF.itemType = nwItemType.TRASH + itemF.itemClass = nwItemClass.TRASH + itemF.isExpanded = False + + itemG = NWItem(theProject) + itemG.itemName = "Characters" + itemG.itemType = nwItemType.ROOT + itemG.itemClass = nwItemClass.CHARACTER + itemG.isExpanded = True + + itemH = NWItem(theProject) + itemH.itemName = "Jane Doe" + itemH.itemType = nwItemType.FILE + itemH.itemClass = nwItemClass.CHARACTER + itemH.itemLayout = nwItemLayout.NOTE + itemH.charCount = 2000 + itemH.wordCount = 400 + itemH.paraCount = 16 + + theItems = [ + ("a000000000001", None, itemA), + ("b000000000001", "a000000000001", itemB), + ("c000000000001", "b000000000001", itemC), + ("c000000000002", "b000000000001", itemD), + ("a000000000002", None, itemE), + ("a000000000003", None, itemF), + ("a000000000004", None, itemG), + ("b000000000002", "a000000000002", itemH), + ] + + return theItems + +@pytest.mark.core +def testCoreTree_BuildTree(dummyGUI, dummyItems): + """Test building a project tree from a list of items. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + theTree.setSeed(42) + assert theTree._handleSeed == 42 + + # Check that tree is empty (calls NWTree.__bool__) + assert not theTree + + # Check for archive and trash folders + assert theTree.trashRoot() is None + assert theTree.archiveRoot() is None + assert not theTree.isTrashRoot("a000000000003") + + aHandles = [] + for tHandle, pHande, nwItem in dummyItems: + aHandles.append(tHandle) + theTree.append(tHandle, pHande, nwItem) + + assert theTree._treeChanged + + # Check that tree is not empty (calls __bool__) + assert theTree + + # Check the number of elements (calls __len__) + assert len(theTree) == len(dummyItems) + + # Check that we have the correct handles + assert theTree.handles() == aHandles + + # Check by iterator (calls __iter__, __next__ and __getitem__) + for theItem, theHandle in zip(theTree, aHandles): + assert theItem.itemHandle == theHandle + + # Check that we have the correct archive and trash folders + assert theTree.trashRoot() == "a000000000003" + assert theTree.archiveRoot() == "a000000000002" + assert theTree.isTrashRoot("a000000000003") + + # Try to add another trash folder + itemT = NWItem(theProject) + itemT.itemName = "Trash" + itemT.itemType = nwItemType.TRASH + itemT.itemClass = nwItemClass.TRASH + itemT.isExpanded = False + + theTree.append("1234567890abc", None, itemT) + assert len(theTree) == len(dummyItems) + + # Generate handle automatically + itemT = NWItem(theProject) + itemT.itemName = "New File" + itemT.itemType = nwItemType.FILE + itemT.itemClass = nwItemClass.NOVEL + itemT.itemLayout = nwItemLayout.SCENE + + theTree.append(None, None, itemT) + assert len(theTree) == len(dummyItems) + 1 + + theList = theTree.handles() + assert theList[-1] == "73475cb40a568" + + # Duplicate handle + itemT = NWItem(theProject) + itemT.itemName = "New File" + itemT.itemType = nwItemType.FILE + itemT.itemClass = nwItemClass.NOVEL + itemT.itemLayout = nwItemLayout.SCENE + + theTree.append("73475cb40a568", None, itemT) + assert len(theTree) == len(dummyItems) + 2 + + theList = theTree.handles() + assert theList[-1] == "44cb730c42048" + + # Delete the last two items + del theTree["dummy"] + assert len(theTree) == len(dummyItems) + 2 + + del theTree["44cb730c42048"] + assert len(theTree) == len(dummyItems) + 1 + assert "44cb730c42048" not in theTree + + del theTree["73475cb40a568"] + assert len(theTree) == len(dummyItems) + assert "73475cb40a568" not in theTree + + del theTree["a000000000001"] + assert len(theTree) == len(dummyItems) - 1 + assert "a000000000001" not in theTree + + del theTree["a000000000002"] + assert len(theTree) == len(dummyItems) - 2 + assert "a000000000002" not in theTree + assert theTree.archiveRoot() is None + + del theTree["a000000000003"] + assert len(theTree) == len(dummyItems) - 3 + assert "a000000000003" not in theTree + assert theTree.trashRoot() is None + +# END Test testCoreTree_BuildTree + +@pytest.mark.core +def testCoreTree_Methods(dummyGUI, dummyItems): + """Test building a project tree from a list of items. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + for tHandle, pHande, nwItem in dummyItems: + theTree.append(tHandle, pHande, nwItem) + + assert len(theTree) == len(dummyItems) + + # Root item lookup + theTree._treeRoots.append("dummy") + assert theTree.findRoot(nwItemClass.WORLD) is None + assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001" + assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004" + + # Check for root uniqueness + assert theTree.checkRootUnique(nwItemClass.CUSTOM) + assert theTree.checkRootUnique(nwItemClass.WORLD) + assert not theTree.checkRootUnique(nwItemClass.NOVEL) + assert not theTree.checkRootUnique(nwItemClass.CHARACTER) + + # Find root item of child item + assert theTree.getRootItem("b000000000001").itemHandle == "a000000000001" + assert theTree.getRootItem("c000000000001").itemHandle == "a000000000001" + assert theTree.getRootItem("c000000000002").itemHandle == "a000000000001" + assert theTree.getRootItem("dummy") is None + + # Get item path + assert theTree.getItemPath("dummy") == [] + assert theTree.getItemPath("c000000000001") == [ + "c000000000001", "b000000000001", "a000000000001" + ] + + # Break the folder parent handle + theTree["b000000000001"].itemParent = "dummy" + assert theTree.getItemPath("c000000000001") == [ + "c000000000001", "b000000000001" + ] + + theTree["b000000000001"].itemParent = "a000000000001" + assert theTree.getItemPath("c000000000001") == [ + "c000000000001", "b000000000001", "a000000000001" + ] + + # Change file layout + assert not theTree.setFileItemLayout("dummy", nwItemLayout.UNNUMBERED) + assert not theTree.setFileItemLayout("b000000000001", nwItemLayout.UNNUMBERED) + assert not theTree.setFileItemLayout("c000000000001", "stuff") + assert theTree.setFileItemLayout("c000000000001", nwItemLayout.UNNUMBERED) + assert theTree["c000000000001"].itemLayout == nwItemLayout.UNNUMBERED + +# END Test testCoreTree_Methods + +@pytest.mark.core +def testCoreTree_MakeHandles(dummyGUI, monkeypatch): + """Test generating item handles. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + theTree.setSeed(42) + + tHandle = theTree._makeHandle() + assert tHandle == "73475cb40a568" + + # Add the next in line to the project to foprce duplicate + theTree._projTree["44cb730c42048"] = None + tHandle = theTree._makeHandle() + assert tHandle == "71ee45a3c0db9" + + # Fix the time() function and force a handle collission + theTree.setSeed(None) + monkeypatch.setattr("nw.core.tree.time", lambda: 123.4) + + tHandle = theTree._makeHandle() + theTree._projTree[tHandle] = None + assert tHandle == "5f466d7afa48b" + + tHandle = theTree._makeHandle() + theTree._projTree[tHandle] = None + assert tHandle == "a79acf4c634a7" + + monkeypatch.undo() + +# END Test testCoreTree_MakeHandles + +@pytest.mark.core +def testCoreTree_Stats(dummyGUI, dummyItems): + """Test project stats methods. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + for tHandle, pHande, nwItem in dummyItems: + theTree.append(tHandle, pHande, nwItem) + + assert len(theTree) == len(dummyItems) + theTree._treeOrder.append("dummy") + + # Count Words + novelWords, noteWords = theTree.sumWords() + assert novelWords == 550 + assert noteWords == 400 + + # Count types + nRoot, nFolder, nFile = theTree.countTypes() + assert nRoot == 3 + assert nFolder == 1 + assert nFile == 3 + +# END Test testCoreTree_Stats + +@pytest.mark.core +def testCoreTree_Reorder(dummyGUI, dummyItems): + """Test changing tree order. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + aHandle = [] + for tHandle, pHande, nwItem in dummyItems: + aHandle.append(tHandle) + theTree.append(tHandle, pHande, nwItem) + + assert len(theTree) == len(dummyItems) + + bHandle = aHandle.copy() + bHandle[2], bHandle[3] = bHandle[3], bHandle[2] + assert aHandle != bHandle + + assert theTree.handles() == aHandle + theTree.setOrder(bHandle) + assert theTree.handles() == bHandle + + theTree.setOrder(bHandle + ["dummy"]) + assert theTree.handles() == bHandle + + theTree._treeOrder.append("dummy") + theTree.setOrder(bHandle) + assert theTree.handles() == bHandle + +# END Test testCoreTree_Reorder + +@pytest.mark.core +def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems): + """Test changing tree order. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + for tHandle, pHande, nwItem in dummyItems: + theTree.append(tHandle, pHande, nwItem) + + assert len(theTree) == len(dummyItems) + + nwXML = etree.Element("novelWriterXML") + theTree.packXML(nwXML) + assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == ( + b"" + b"" + b"" + b"NovelROOTNOVELNone" + b"True" + b"" + b"Act OneFOLDERNOVELNone" + b"True" + b"" + b"Chapter OneFILENOVELNone" + b"TrueUNNUMBERED300" + b"5020" + b"" + b"Scene OneFILENOVELNone" + b"TrueSCENE3000" + b"500200" + b"" + b"OuttakesROOTARCHIVENone" + b"False" + b"" + b"TrashTRASHTRASHNone" + b"False" + b"" + b"CharactersROOTCHARACTERNone" + b"True" + b"" + b"Jane DoeFILECHARACTERNone" + b"TrueNOTE2000" + b"400160" + b"" + ) + + theTree.clear() + assert len(theTree) == 0 + assert not theTree.unpackXML(nwXML) + assert theTree.unpackXML(nwXML[0]) + assert len(theTree) == len(dummyItems) + +# END Test testCoreTree_XMLPackUnpack + +@pytest.mark.core +def testCoreTree_ToCFile(dummyGUI, dummyItems, tmpDir, monkeypatch): + """Test writing the ToC.txt file. + """ + theProject = NWProject(dummyGUI) + theTree = NWTree(theProject) + + for tHandle, pHande, nwItem in dummyItems: + theTree.append(tHandle, pHande, nwItem) + + assert len(theTree) == len(dummyItems) + theTree._treeOrder.append("dummy") + + monkeypatch.setattr("os.path.isfile", lambda *args: True) + + theProject.projContent = "content" + theProject.projPath = None + assert not theTree.writeToCFile() + + theProject.projPath = tmpDir + assert theTree.writeToCFile() + + with open(os.path.join(tmpDir, nwFiles.TOC_TXT), mode="r", encoding="utf8") as inFile: + assert inFile.read() == ( + "\n" + "Table of Contents\n" + "=================\n" + "\n" + "File Name Class Layout Document Label\n" + "-------------------------------------------------------------\n" + "content/a000000000001.nwd NOVEL NO_LAYOUT Novel\n" + "content/b000000000001.nwd NOVEL NO_LAYOUT Act One\n" + "content/c000000000001.nwd NOVEL UNNUMBERED Chapter One\n" + "content/c000000000002.nwd NOVEL SCENE Scene One\n" + "content/a000000000002.nwd ARCHIVE NO_LAYOUT Outtakes\n" + "content/a000000000003.nwd TRASH NO_LAYOUT Trash\n" + "content/a000000000004.nwd CHARACTER NO_LAYOUT Characters\n" + "content/b000000000002.nwd CHARACTER NOTE Jane Doe\n" + ) + +# END Test testCoreTree_ToCFile From b4b359475dd3bf62e1d38d42802cf34290afbf0f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 09:41:56 +0100 Subject: [PATCH 04/22] Revert some changes to the NWTree.append class --- nw/core/tree.py | 8 +++---- tests/test_core_tree.py | 48 +++++++++++++++++------------------------ 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/nw/core/tree.py b/nw/core/tree.py index b76b6678..43678b73 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -86,8 +86,8 @@ class NWTree(): tHandle = self._makeHandle() if tHandle in self._projTree: - logger.warning("Duplicate handle %s detected, generating new" % tHandle) - tHandle = self._makeHandle() + logger.warning("Duplicate handle %s detected, skipping" % tHandle) + return False logger.verbose("Adding item %s with parent %s" % (str(tHandle), str(pHandle))) @@ -107,13 +107,13 @@ class NWTree(): self._trashRoot = tHandle else: logger.error("Only one trash folder allowed") - return + return False self._projTree[tHandle] = nwItem self._treeOrder.append(tHandle) self._setTreeChanged(True) - return + return True def packXML(self, xParent): """Pack the content of the tree into the provided XML object. In diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py index 1eff405d..f8c9e42b 100644 --- a/tests/test_core_tree.py +++ b/tests/test_core_tree.py @@ -107,7 +107,7 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems): aHandles = [] for tHandle, pHande, nwItem in dummyItems: aHandles.append(tHandle) - theTree.append(tHandle, pHande, nwItem) + assert theTree.append(tHandle, pHande, nwItem) assert theTree._treeChanged @@ -136,7 +136,7 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems): itemT.itemClass = nwItemClass.TRASH itemT.isExpanded = False - theTree.append("1234567890abc", None, itemT) + assert not theTree.append("1234567890abc", None, itemT) assert len(theTree) == len(dummyItems) # Generate handle automatically @@ -146,37 +146,26 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems): itemT.itemClass = nwItemClass.NOVEL itemT.itemLayout = nwItemLayout.SCENE - theTree.append(None, None, itemT) + assert theTree.append(None, None, itemT) assert len(theTree) == len(dummyItems) + 1 theList = theTree.handles() assert theList[-1] == "73475cb40a568" - # Duplicate handle - itemT = NWItem(theProject) - itemT.itemName = "New File" - itemT.itemType = nwItemType.FILE - itemT.itemClass = nwItemClass.NOVEL - itemT.itemLayout = nwItemLayout.SCENE - - theTree.append("73475cb40a568", None, itemT) - assert len(theTree) == len(dummyItems) + 2 - - theList = theTree.handles() - assert theList[-1] == "44cb730c42048" - - # Delete the last two items - del theTree["dummy"] - assert len(theTree) == len(dummyItems) + 2 - - del theTree["44cb730c42048"] + # Try to add existing handle + assert not theTree.append("73475cb40a568", None, itemT) assert len(theTree) == len(dummyItems) + 1 - assert "44cb730c42048" not in theTree + # Delete a non-existing item + del theTree["dummy"] + assert len(theTree) == len(dummyItems) + 1 + + # Delete the last item del theTree["73475cb40a568"] assert len(theTree) == len(dummyItems) assert "73475cb40a568" not in theTree + # Delete the Novel, Archive and Trash folders del theTree["a000000000001"] assert len(theTree) == len(dummyItems) - 1 assert "a000000000001" not in theTree @@ -407,7 +396,15 @@ def testCoreTree_ToCFile(dummyGUI, dummyItems, tmpDir, monkeypatch): assert len(theTree) == len(dummyItems) theTree._treeOrder.append("dummy") - monkeypatch.setattr("os.path.isfile", lambda *args: True) + def dummyIsFile(fileName): + """Return True for items that are files in novelWriter and + should thus also be files in the project folder structure. + """ + dItem = theTree[fileName[8:21]] + assert dItem is not None + return dItem.itemType == nwItemType.FILE + + monkeypatch.setattr("os.path.isfile", dummyIsFile) theProject.projContent = "content" theProject.projPath = None @@ -424,13 +421,8 @@ def testCoreTree_ToCFile(dummyGUI, dummyItems, tmpDir, monkeypatch): "\n" "File Name Class Layout Document Label\n" "-------------------------------------------------------------\n" - "content/a000000000001.nwd NOVEL NO_LAYOUT Novel\n" - "content/b000000000001.nwd NOVEL NO_LAYOUT Act One\n" "content/c000000000001.nwd NOVEL UNNUMBERED Chapter One\n" "content/c000000000002.nwd NOVEL SCENE Scene One\n" - "content/a000000000002.nwd ARCHIVE NO_LAYOUT Outtakes\n" - "content/a000000000003.nwd TRASH NO_LAYOUT Trash\n" - "content/a000000000004.nwd CHARACTER NO_LAYOUT Characters\n" "content/b000000000002.nwd CHARACTER NOTE Jane Doe\n" ) From 2c0eaeb55727dac8b9adec289871cbc9fa8b8f92 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 10:23:35 +0100 Subject: [PATCH 05/22] Fix old tests --- nw/core/item.py | 2 +- tests/conftest.py | 275 ++++++++++++++------------- tests/reference/proj/1_nwProject.nwx | 16 +- tests/reference/proj/2_nwProject.nwx | 24 +-- tests/reference/proj/3_nwProject.nwx | 20 +- tests/reference/proj/4_nwProject.nwx | 46 ++--- tests/reference/proj/5_nwProject.nwx | 28 +-- tests/test_config.py | 36 ++-- tests/test_core_tree.py | 16 +- tests/test_dialogs.py | 50 ++--- tests/test_error.py | 4 +- tests/test_gui.py | 58 +++--- tests/test_index.py | 30 +-- tests/test_project.py | 84 ++++---- 14 files changed, 345 insertions(+), 344 deletions(-) diff --git a/nw/core/item.py b/nw/core/item.py index ead16ab8..477f2df9 100644 --- a/nw/core/item.py +++ b/nw/core/item.py @@ -43,7 +43,7 @@ class NWItem(): self.itemName = "" self.itemHandle = None self.itemParent = None - self.itemOrder = None + self.itemOrder = 0 self.itemType = nwItemType.NO_TYPE self.itemClass = nwItemClass.NO_CLASS self.itemLayout = nwItemLayout.NO_LAYOUT diff --git a/tests/conftest.py b/tests/conftest.py index 97aafd7d..863818eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ import os from nwdummy import DummyMain -# from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtWidgets import QMessageBox sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) @@ -33,28 +33,10 @@ def tmpDir(): os.mkdir(tempDir) return tempDir -# @pytest.fixture(scope="session") -# def nwRef(): -# """The folder where all the reference files are stored for verifying -# the results of tests. -# """ -# testDir = os.path.dirname(__file__) -# refDir = os.path.join(testDir, "reference") -# return refDir - ## # novelWriter Objects ## -# @pytest.fixture(scope="session") -# def tmpConf(nwTemp): -# """Create a temporary novelWriter configuration object. -# """ -# theConf = Config() -# theConf.initConfig(nwTemp, nwTemp) -# theConf.setLastPath("") -# return theConf - @pytest.fixture(scope="session") def tmpConf(tmpDir): """Create a temporary novelWriter configuration object. @@ -64,15 +46,6 @@ def tmpConf(tmpDir): theConf.setLastPath("") return theConf -# @pytest.fixture(scope="session") -# def nwConf(nwRef, nwTemp): -# """Temporary novelWriter configuration used for the dummy instance -# of novelWriter's main GUI. -# """ -# theConf = Config() -# theConf.initConfig(nwRef, nwTemp) -# return theConf - @pytest.fixture(scope="session") def dummyGUI(tmpConf): """Create a dummy instance of novelWriter's main GUI class. @@ -81,131 +54,159 @@ def dummyGUI(tmpConf): theDummy.mainConf = tmpConf return theDummy +# =============================================================================================== # + +## +# Core Test Folders +## + +@pytest.fixture(scope="session") +def nwRef(): + """The folder where all the reference files are stored for verifying + the results of tests. + """ + testDir = os.path.dirname(__file__) + refDir = os.path.join(testDir, "reference") + return refDir + +## +# novelWriter Objects +## + +@pytest.fixture(scope="session") +def nwConf(nwRef, tmpDir): + """Temporary novelWriter configuration used for the dummy instance + of novelWriter's main GUI. + """ + theConf = Config() + theConf.initConfig(nwRef, tmpDir) + return theConf + ## # Temporary Test Folders ## -# @pytest.fixture(scope="session") -# def nwTempProj(nwTemp): -# """A temporary folder for project tests. -# """ -# projDir = os.path.join(nwTemp, "proj") -# if not os.path.isdir(projDir): -# os.mkdir(projDir) -# return projDir +@pytest.fixture(scope="session") +def nwTempProj(tmpDir): + """A temporary folder for project tests. + """ + projDir = os.path.join(tmpDir, "proj") + if not os.path.isdir(projDir): + os.mkdir(projDir) + return projDir -# @pytest.fixture(scope="session") -# def nwTempGUI(nwTemp): -# """A temporary folder for GUI tests. -# """ -# guiDir = os.path.join(nwTemp, "gui") -# if not os.path.isdir(guiDir): -# os.mkdir(guiDir) -# return guiDir +@pytest.fixture(scope="session") +def nwTempGUI(tmpDir): + """A temporary folder for GUI tests. + """ + guiDir = os.path.join(tmpDir, "gui") + if not os.path.isdir(guiDir): + os.mkdir(guiDir) + return guiDir -# @pytest.fixture(scope="session") -# def nwTempBuild(nwTemp): -# """A temporary folder for build tests. -# """ -# buildDir = os.path.join(nwTemp, "build") -# if not os.path.isdir(buildDir): -# os.mkdir(buildDir) -# return buildDir +@pytest.fixture(scope="session") +def nwTempBuild(tmpDir): + """A temporary folder for build tests. + """ + buildDir = os.path.join(tmpDir, "build") + if not os.path.isdir(buildDir): + os.mkdir(buildDir) + return buildDir -# @pytest.fixture(scope="function") -# def nwFuncTemp(nwTemp): -# """A temporary folder for a single test function. -# """ -# funcDir = os.path.join(nwTemp, "ftemp") -# if os.path.isdir(funcDir): -# shutil.rmtree(funcDir) -# if not os.path.isdir(funcDir): -# os.mkdir(funcDir) -# yield funcDir -# if os.path.isdir(funcDir): -# shutil.rmtree(funcDir) -# return +@pytest.fixture(scope="function") +def nwFuncTemp(tmpDir): + """A temporary folder for a single test function. + """ + funcDir = os.path.join(tmpDir, "ftemp") + if os.path.isdir(funcDir): + shutil.rmtree(funcDir) + if not os.path.isdir(funcDir): + os.mkdir(funcDir) + yield funcDir + if os.path.isdir(funcDir): + shutil.rmtree(funcDir) + return ## # Temp Folders for Projects ## -# @pytest.fixture(scope="function") -# def nwMinimal(nwTemp): -# """A minimal novelWriter example project. -# """ -# testDir = os.path.dirname(__file__) -# minimalStore = os.path.join(testDir, "minimal") -# minimalDir = os.path.join(nwTemp, "minimal") -# if os.path.isdir(minimalDir): -# shutil.rmtree(minimalDir) -# shutil.copytree(minimalStore, minimalDir) -# cacheDir = os.path.join(minimalDir, "cache") -# if os.path.isdir(cacheDir): -# shutil.rmtree(cacheDir) -# metaDir = os.path.join(minimalDir, "meta") -# if os.path.isdir(metaDir): -# shutil.rmtree(metaDir) -# yield minimalDir -# if os.path.isdir(minimalDir): -# shutil.rmtree(minimalDir) -# return +@pytest.fixture(scope="function") +def nwMinimal(tmpDir): + """A minimal novelWriter example project. + """ + testDir = os.path.dirname(__file__) + minimalStore = os.path.join(testDir, "minimal") + minimalDir = os.path.join(tmpDir, "minimal") + if os.path.isdir(minimalDir): + shutil.rmtree(minimalDir) + shutil.copytree(minimalStore, minimalDir) + cacheDir = os.path.join(minimalDir, "cache") + if os.path.isdir(cacheDir): + shutil.rmtree(cacheDir) + metaDir = os.path.join(minimalDir, "meta") + if os.path.isdir(metaDir): + shutil.rmtree(metaDir) + yield minimalDir + if os.path.isdir(minimalDir): + shutil.rmtree(minimalDir) + return -# @pytest.fixture(scope="function") -# def nwLipsum(nwTemp): -# """A medium sized novelWriter example project with a lot of Lorem -# Ipsum dummy text. -# """ -# testDir = os.path.dirname(__file__) -# lipsumStore = os.path.join(testDir, "lipsum") -# lipsumDir = os.path.join(nwTemp, "lipsum") -# if os.path.isdir(lipsumDir): -# shutil.rmtree(lipsumDir) -# shutil.copytree(lipsumStore, lipsumDir) -# cacheDir = os.path.join(lipsumDir, "cache") -# if os.path.isdir(cacheDir): -# shutil.rmtree(cacheDir) -# metaDir = os.path.join(lipsumDir, "meta") -# if os.path.isdir(metaDir): -# shutil.rmtree(metaDir) -# yield lipsumDir -# if os.path.isdir(lipsumDir): -# shutil.rmtree(lipsumDir) -# return +@pytest.fixture(scope="function") +def nwLipsum(tmpDir): + """A medium sized novelWriter example project with a lot of Lorem + Ipsum dummy text. + """ + testDir = os.path.dirname(__file__) + lipsumStore = os.path.join(testDir, "lipsum") + lipsumDir = os.path.join(tmpDir, "lipsum") + if os.path.isdir(lipsumDir): + shutil.rmtree(lipsumDir) + shutil.copytree(lipsumStore, lipsumDir) + cacheDir = os.path.join(lipsumDir, "cache") + if os.path.isdir(cacheDir): + shutil.rmtree(cacheDir) + metaDir = os.path.join(lipsumDir, "meta") + if os.path.isdir(metaDir): + shutil.rmtree(metaDir) + yield lipsumDir + if os.path.isdir(lipsumDir): + shutil.rmtree(lipsumDir) + return -# @pytest.fixture(scope="function") -# def nwOldProj(nwTemp): -# """A minimal movelWriter project using the old folder structure. -# """ -# testDir = os.path.dirname(__file__) -# oldProjStore = os.path.join(testDir, "oldproj") -# oldProjDir = os.path.join(nwTemp, "oldproj") -# if os.path.isdir(oldProjDir): -# shutil.rmtree(oldProjDir) -# shutil.copytree(oldProjStore, oldProjDir) -# yield oldProjDir -# if os.path.isdir(oldProjDir): -# shutil.rmtree(oldProjDir) -# return +@pytest.fixture(scope="function") +def nwOldProj(tmpDir): + """A minimal movelWriter project using the old folder structure. + """ + testDir = os.path.dirname(__file__) + oldProjStore = os.path.join(testDir, "oldproj") + oldProjDir = os.path.join(tmpDir, "oldproj") + if os.path.isdir(oldProjDir): + shutil.rmtree(oldProjDir) + shutil.copytree(oldProjStore, oldProjDir) + yield oldProjDir + if os.path.isdir(oldProjDir): + shutil.rmtree(oldProjDir) + return ## # Monkey Patch Dialogs ## -# @pytest.fixture(scope="function") -# def yesToAll(monkeypatch): -# """Make the message boxes/questions always say yes. -# """ -# monkeypatch.setattr( -# QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes -# ) -# monkeypatch.setattr( -# QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes -# ) -# monkeypatch.setattr( -# QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes -# ) -# monkeypatch.setattr( -# QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes -# ) -# return +@pytest.fixture(scope="function") +def yesToAll(monkeypatch): + """Make the message boxes/questions always say yes. + """ + monkeypatch.setattr( + QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes + ) + monkeypatch.setattr( + QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes + ) + monkeypatch.setattr( + QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes + ) + monkeypatch.setattr( + QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes + ) + return diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx index 61ece12f..d8b774bb 100644 --- a/tests/reference/proj/1_nwProject.nwx +++ b/tests/reference/proj/1_nwProject.nwx @@ -39,35 +39,35 @@ - + Novel ROOT NOVEL New False - + Plot ROOT PLOT New False - + Characters ROOT CHARACTER New False - + World ROOT WORLD New False - + Title Page FILE NOVEL @@ -79,14 +79,14 @@ 0 0 - + New Chapter FOLDER NOVEL New False - + New Chapter FILE NOVEL @@ -98,7 +98,7 @@ 0 0 - + New Scene FILE NOVEL diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx index cc7b54ea..9ea0617f 100644 --- a/tests/reference/proj/2_nwProject.nwx +++ b/tests/reference/proj/2_nwProject.nwx @@ -39,35 +39,35 @@ - + Novel ROOT NOVEL New False - + Plot ROOT PLOT New False - + Characters ROOT CHARACTER New False - + World ROOT WORLD New False - + Title Page FILE NOVEL @@ -79,14 +79,14 @@ 0 0 - + New Chapter FOLDER NOVEL New False - + New Chapter FILE NOVEL @@ -98,7 +98,7 @@ 0 0 - + New Scene FILE NOVEL @@ -110,28 +110,28 @@ 0 0 - + Timeline ROOT TIMELINE New False - + Object ROOT OBJECT New False - + Custom1 ROOT CUSTOM New False - + Custom2 ROOT CUSTOM diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/proj/3_nwProject.nwx index c9e450d6..66488434 100644 --- a/tests/reference/proj/3_nwProject.nwx +++ b/tests/reference/proj/3_nwProject.nwx @@ -39,35 +39,35 @@ - + Novel ROOT NOVEL New False - + Plot ROOT PLOT New False - + Characters ROOT CHARACTER New False - + World ROOT WORLD New False - + Title Page FILE NOVEL @@ -79,14 +79,14 @@ 0 0 - + New Chapter FOLDER NOVEL New False - + New Chapter FILE NOVEL @@ -98,7 +98,7 @@ 0 0 - + New Scene FILE NOVEL @@ -110,7 +110,7 @@ 0 0 - + Hello FILE NOVEL @@ -122,7 +122,7 @@ 0 0 - + Jane FILE CHARACTER diff --git a/tests/reference/proj/4_nwProject.nwx b/tests/reference/proj/4_nwProject.nwx index 4c9c30eb..fc7e7ab2 100644 --- a/tests/reference/proj/4_nwProject.nwx +++ b/tests/reference/proj/4_nwProject.nwx @@ -41,56 +41,56 @@ - + Novel ROOT NOVEL New False - + Plot ROOT PLOT New False - + Characters ROOT CHARACTER New False - + Locations ROOT WORLD New False - + Timeline ROOT TIMELINE New False - + Objects ROOT OBJECT New False - + Entity ROOT ENTITY New False - + Title Page FILE NOVEL @@ -102,14 +102,14 @@ 0 0 - + Chapter 1 FOLDER NOVEL New False - + Chapter 1 FILE NOVEL @@ -121,7 +121,7 @@ 0 0 - + Scene 1.1 FILE NOVEL @@ -133,7 +133,7 @@ 0 0 - + Scene 1.2 FILE NOVEL @@ -145,7 +145,7 @@ 0 0 - + Scene 1.3 FILE NOVEL @@ -157,14 +157,14 @@ 0 0 - + Chapter 2 FOLDER NOVEL New False - + Chapter 2 FILE NOVEL @@ -176,7 +176,7 @@ 0 0 - + Scene 2.1 FILE NOVEL @@ -188,7 +188,7 @@ 0 0 - + Scene 2.2 FILE NOVEL @@ -200,7 +200,7 @@ 0 0 - + Scene 2.3 FILE NOVEL @@ -212,14 +212,14 @@ 0 0 - + Chapter 3 FOLDER NOVEL New False - + Chapter 3 FILE NOVEL @@ -231,7 +231,7 @@ 0 0 - + Scene 3.1 FILE NOVEL @@ -243,7 +243,7 @@ 0 0 - + Scene 3.2 FILE NOVEL @@ -255,7 +255,7 @@ 0 0 - + Scene 3.3 FILE NOVEL diff --git a/tests/reference/proj/5_nwProject.nwx b/tests/reference/proj/5_nwProject.nwx index 9935736e..11d007aa 100644 --- a/tests/reference/proj/5_nwProject.nwx +++ b/tests/reference/proj/5_nwProject.nwx @@ -41,56 +41,56 @@ - + Novel ROOT NOVEL New False - + Plot ROOT PLOT New False - + Characters ROOT CHARACTER New False - + Locations ROOT WORLD New False - + Timeline ROOT TIMELINE New False - + Objects ROOT OBJECT New False - + Entity ROOT ENTITY New False - + Title Page FILE NOVEL @@ -102,7 +102,7 @@ 0 0 - + Scene 1 FILE NOVEL @@ -114,7 +114,7 @@ 0 0 - + Scene 2 FILE NOVEL @@ -126,7 +126,7 @@ 0 0 - + Scene 3 FILE NOVEL @@ -138,7 +138,7 @@ 0 0 - + Scene 4 FILE NOVEL @@ -150,7 +150,7 @@ 0 0 - + Scene 5 FILE NOVEL @@ -162,7 +162,7 @@ 0 0 - + Scene 6 FILE NOVEL diff --git a/tests/test_config.py b/tests/test_config.py index 5d72cdfd..ad83ddd5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,11 +8,11 @@ import os from nwtools import cmpFiles @pytest.mark.core -def testConfigCore(tmpConf, nwTemp, nwRef): +def testConfigCore(tmpConf, tmpDir, nwRef): refConf = os.path.join(nwRef, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - assert tmpConf.confPath == nwTemp + assert tmpConf.confPath == tmpDir assert tmpConf.saveConfig() assert cmpFiles(testConf, refConf, [2, 9]) assert not tmpConf.confChanged @@ -21,29 +21,29 @@ def testConfigCore(tmpConf, nwTemp, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetConfPath(tmpConf, nwTemp): +def testConfigSetConfPath(tmpConf, tmpDir): assert tmpConf.setConfPath(None) assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow")) - assert tmpConf.setConfPath(os.path.join(nwTemp, "novelwriter.conf")) - assert tmpConf.confPath == nwTemp + assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf")) + assert tmpConf.confPath == tmpDir assert tmpConf.confFile == "novelwriter.conf" assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetDataPath(tmpConf, nwTemp): +def testConfigSetDataPath(tmpConf, tmpDir): assert tmpConf.setDataPath(None) assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow")) - assert tmpConf.setDataPath(nwTemp) - assert tmpConf.dataPath == nwTemp + assert tmpConf.setDataPath(tmpDir) + assert tmpConf.dataPath == tmpDir assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetWinSize(tmpConf, nwTemp, nwRef): +def testConfigSetWinSize(tmpConf, tmpDir, nwRef): refConf = os.path.join(nwRef, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") tmpConf.guiScale = 1.0 - assert tmpConf.confPath == nwTemp + assert tmpConf.confPath == tmpDir assert tmpConf.setWinSize(1205, 655) assert not tmpConf.confChanged assert tmpConf.setWinSize(70, 70) @@ -55,11 +55,11 @@ def testConfigSetWinSize(tmpConf, nwTemp, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef): +def testConfigSetTreeColWidths(tmpConf, tmpDir, nwRef): refConf = os.path.join(nwRef, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - assert tmpConf.confPath == nwTemp + assert tmpConf.confPath == tmpDir tmpConf.guiScale = 1.0 assert tmpConf.setTreeColWidths([10, 20, 25]) @@ -77,11 +77,11 @@ def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetPanePos(tmpConf, nwTemp, nwRef): +def testConfigSetPanePos(tmpConf, tmpDir, nwRef): refConf = os.path.join(nwRef, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - assert tmpConf.confPath == nwTemp + assert tmpConf.confPath == tmpDir tmpConf.guiScale = 2.0 assert tmpConf.setMainPanePos([200, 700]) @@ -113,11 +113,11 @@ def testConfigSetPanePos(tmpConf, nwTemp, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigFlags(tmpConf, nwTemp, nwRef): +def testConfigFlags(tmpConf, tmpDir, nwRef): refConf = os.path.join(nwRef, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - assert tmpConf.confPath == nwTemp + assert tmpConf.confPath == tmpDir assert not tmpConf.setShowRefPanel(False) assert tmpConf.setShowRefPanel(True) @@ -137,8 +137,8 @@ def testConfigFlags(tmpConf, nwTemp, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testTextSizes(tmpConf, nwTemp, nwRef): - assert tmpConf.confPath == nwTemp +def testTextSizes(tmpConf, tmpDir, nwRef): + assert tmpConf.confPath == tmpDir tmpConf.guiScale = 2.0 assert tmpConf.getTextWidth() == 1200 diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py index f8c9e42b..c3b68964 100644 --- a/tests/test_core_tree.py +++ b/tests/test_core_tree.py @@ -345,30 +345,30 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems): assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == ( b"" b"" - b"" + b"" b"NovelROOTNOVELNone" b"True" - b"" + b"" b"Act OneFOLDERNOVELNone" b"True" - b"" + b"" b"Chapter OneFILENOVELNone" b"TrueUNNUMBERED300" b"5020" - b"" + b"" b"Scene OneFILENOVELNone" b"TrueSCENE3000" b"500200" - b"" + b"" b"OuttakesROOTARCHIVENone" b"False" - b"" + b"" b"TrashTRASHTRASHNone" b"False" - b"" + b"" b"CharactersROOTCHARACTERNone" b"True" - b"" + b"" b"Jane DoeFILECHARACTERNone" b"TrueNOTE2000" b"400160" diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index 4c1f1885..5e9a319f 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -30,8 +30,8 @@ typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp]) +def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwRef, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -145,8 +145,8 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR nwGUI.closeMain() @pytest.mark.gui -def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp]) +def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -218,8 +218,8 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, n nwGUI.closeMain() @pytest.mark.gui -def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp]) +def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -357,8 +357,8 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testAboutBox(qtbot, monkeypatch, nwFuncTemp, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) +def testAboutBox(qtbot, monkeypatch, nwFuncTemp, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -397,9 +397,9 @@ def testAboutBox(qtbot, monkeypatch, nwFuncTemp, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp): +def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -589,9 +589,9 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp): nwGUI.closeMain() @pytest.mark.gui -def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef, nwTemp): +def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -740,7 +740,7 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef nwGUI.closeMain() @pytest.mark.gui -def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp): +def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): if sys.platform.startswith("darwin"): # Disable for macOS because the test segfaults on QWizard.show() @@ -752,7 +752,7 @@ def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp): ProjWizardCustomPage, ProjWizardFinalPage ) - nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -937,8 +937,8 @@ def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp): nwGUI.close() @pytest.mark.gui -def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp]) +def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1017,8 +1017,8 @@ def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp): nwGUI.close() @pytest.mark.gui -def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp, nwRef, tmpConf): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp]) +def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir, nwRef, tmpConf): + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1205,7 +1205,7 @@ def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp, nwRef, tmpC refConf = os.path.join(nwRef, "novelwriter_prefs.conf") projConf = os.path.join(nwGUI.mainConf.confPath, "novelwriter.conf") - testConf = os.path.join(nwTemp, "novelwriter_prefs.conf") + testConf = os.path.join(tmpDir, "novelwriter_prefs.conf") copyfile(projConf, testConf) ignoreLines = [ 2, # Timestamp @@ -1216,8 +1216,8 @@ def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp, nwRef, tmpC assert cmpFiles(testConf, refConf, ignoreLines) @pytest.mark.gui -def testQuotesDialog(qtbot, yesToAll, nwMinimal, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal]) +def testQuotesDialog(qtbot, yesToAll, nwMinimal, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1246,15 +1246,15 @@ def testQuotesDialog(qtbot, yesToAll, nwMinimal, nwTemp): nwGUI.close() @pytest.mark.gui -def testDialogsOpenClose(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal]) +def testDialogsOpenClose(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) qtbot.wait(stepDelay) - monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: nwTemp) - assert nwGUI.selectProjectPath() == nwTemp + monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: tmpDir) + assert nwGUI.selectProjectPath() == tmpDir # qtbot.stopForInteraction() nwGUI.closeMain() diff --git a/tests/test_error.py b/tests/test_error.py index 179efa68..7ad07269 100644 --- a/tests/test_error.py +++ b/tests/test_error.py @@ -10,9 +10,9 @@ from PyQt5.QtWidgets import qApp from nw.error import NWErrorMessage, exceptionHandler @pytest.mark.error -def testErrorDialog(qtbot, nwFuncTemp, nwTemp): +def testErrorDialog(qtbot, nwFuncTemp, tmpDir): qApp.closeAllWindows() - nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) diff --git a/tests/test_gui.py b/tests/test_gui.py index fcf96c06..2b10b5de 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -27,11 +27,11 @@ typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): +def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): # Defaults nwGUI = nw.main( - ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp, "--style=Fusion"] + ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir, "--style=Fusion"] ) assert nw.logger.getEffectiveLevel() == logging.WARNING nwGUI.closeMain() @@ -39,21 +39,21 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): # Log Levels nwGUI = nw.main( - ["--testmode", "--info", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--info", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.INFO nwGUI.closeMain() nwGUI.close() nwGUI = nw.main( - ["--testmode", "--debug", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--debug", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.DEBUG nwGUI.closeMain() nwGUI.close() nwGUI = nw.main( - ["--testmode", "--verbose", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--verbose", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == 5 nwGUI.closeMain() @@ -62,7 +62,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): # Help and Version with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--help", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--help", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -70,7 +70,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--version", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--version", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -79,7 +79,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): # Invalid options with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--invalid", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--invalid", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -92,7 +92,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000) with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp] + ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -103,9 +103,9 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp): monkeypatch.undo() @pytest.mark.gui -def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp): +def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -414,9 +414,9 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp): nwGUI.close() @pytest.mark.gui -def testDocViewer(qtbot, yesToAll, nwLipsum, nwTemp): +def testDocViewer(qtbot, yesToAll, nwLipsum, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -566,9 +566,9 @@ def testDocViewer(qtbot, yesToAll, nwLipsum, nwTemp): nwGUI.close() @pytest.mark.gui -def testProjectTree(qtbot, yesToAll, nwMinimal, nwTemp): +def testProjectTree(qtbot, yesToAll, nwMinimal, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -690,9 +690,9 @@ def testProjectTree(qtbot, yesToAll, nwMinimal, nwTemp): nwGUI.close() @pytest.mark.gui -def testEditFormatMenu(qtbot, yesToAll, nwLipsum, nwTemp): +def testEditFormatMenu(qtbot, yesToAll, nwLipsum, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -927,9 +927,9 @@ def testEditFormatMenu(qtbot, yesToAll, nwLipsum, nwTemp): nwGUI.close() @pytest.mark.gui -def testContextMenu(qtbot, yesToAll, nwLipsum, nwTemp): +def testContextMenu(qtbot, yesToAll, nwLipsum, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1013,8 +1013,8 @@ def testContextMenu(qtbot, yesToAll, nwLipsum, nwTemp): nwGUI.close() @pytest.mark.gui -def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, nwTemp): - nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp]) +def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1162,7 +1162,7 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, nwTemp): assert not nwGUI.importDocument() # Then a valid path, but bot a file that exists - theFile = os.path.join(nwTemp, "import.txt") + theFile = os.path.join(tmpDir, "import.txt") monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (theFile, "")) assert not nwGUI.importDocument() @@ -1212,9 +1212,9 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, nwTemp): nwGUI.close() @pytest.mark.gui -def testTextSearch(qtbot, monkeypatch, yesToAll, nwLipsum, nwTemp): +def testTextSearch(qtbot, monkeypatch, yesToAll, nwLipsum, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1392,9 +1392,9 @@ def testTextSearch(qtbot, monkeypatch, yesToAll, nwLipsum, nwTemp): nwGUI.close() @pytest.mark.gui -def testOutline(qtbot, yesToAll, nwLipsum, nwTemp): +def testOutline(qtbot, yesToAll, nwLipsum, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1453,9 +1453,9 @@ def testOutline(qtbot, yesToAll, nwLipsum, nwTemp): nwGUI.close() @pytest.mark.gui -def testThemes(qtbot, yesToAll, nwMinimal, nwTemp): +def testThemes(qtbot, yesToAll, nwMinimal, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) @@ -1478,7 +1478,7 @@ def testThemes(qtbot, yesToAll, nwMinimal, nwTemp): # Re-open assert nw.CONFIG.confPath == nwMinimal - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal]) + nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) assert nwGUI.mainConf.confPath == nwMinimal qtbot.addWidget(nwGUI) nwGUI.show() diff --git a/tests/test_index.py b/tests/test_index.py index 9fabae5b..dd5add5c 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -15,7 +15,7 @@ from nw.core.index import NWIndex from nw.constants import nwItemClass, nwItemLayout @pytest.mark.project -def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef): +def testIndexBuildCheck(monkeypatch, nwLipsum, dummyGUI, nwTempProj, nwRef): """Test core functionality of scaning, saving, loading and checking the index cache file. """ @@ -23,13 +23,13 @@ def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef): testFile = os.path.join(nwTempProj, "1_tagsIndex.json") refFile = os.path.join(nwRef, "proj", "1_tagsIndex.json") - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwLipsum) monkeypatch.setattr("nw.core.index.time", lambda: 123.4) - theIndex = NWIndex(theProject, nwDummy) + theIndex = NWIndex(theProject, dummyGUI) notIndexable = { "b3643d0f92e32": False, # Novel ROOT "45e6b01ca35c1": False, # Chapter One FOLDER @@ -139,14 +139,14 @@ def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef): assert cmpFiles(testFile, refFile) @pytest.mark.project -def testIndexScanThis(nwMinimal, nwDummy): +def testIndexScanThis(nwMinimal, dummyGUI): """Test the tag scanner function scanThis. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) - theIndex = NWIndex(theProject, nwDummy) + theIndex = NWIndex(theProject, dummyGUI) isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") assert not isValid @@ -188,14 +188,14 @@ def testIndexScanThis(nwMinimal, nwDummy): assert theProject.closeProject() @pytest.mark.project -def testIndexCheckThese(nwMinimal, nwDummy): +def testIndexCheckThese(nwMinimal, dummyGUI): """Test the tag checker function checkThese. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) - theIndex = NWIndex(theProject, nwDummy) + theIndex = NWIndex(theProject, dummyGUI) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") nItem = theProject.projTree[nHandle] @@ -224,14 +224,14 @@ def testIndexCheckThese(nwMinimal, nwDummy): assert theProject.closeProject() @pytest.mark.project -def testIndexScanText(nwMinimal, nwDummy): +def testIndexScanText(nwMinimal, dummyGUI): """Check the index data extraction functions. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) - theIndex = NWIndex(theProject, nwDummy) + theIndex = NWIndex(theProject, dummyGUI) # Some items for fail to scan tests dHandle = theProject.newFolder("Folder", nwItemClass.NOVEL, "a508bb932959c") @@ -399,14 +399,14 @@ def testIndexScanText(nwMinimal, nwDummy): assert theProject.closeProject() @pytest.mark.project -def testIndexExtractData(nwMinimal, nwDummy): +def testIndexExtractData(nwMinimal, dummyGUI): """Check the index data extraction functions. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) - theIndex = NWIndex(theProject, nwDummy) + theIndex = NWIndex(theProject, dummyGUI) nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c") cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3") diff --git a/tests/test_project.py b/tests/test_project.py index 5ed9d261..b8a49aa9 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -16,14 +16,14 @@ from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple from nw.constants import nwConst, nwItemClass, nwItemType, nwItemLayout, nwFiles @pytest.mark.project -def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy): +def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, tmpDir, dummyGUI): """Test that a basic project can be created, and opened and saved. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "1_nwProject.nwx") refFile = os.path.join(nwRef, "proj", "1_nwProject.nwx") - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) # Setting no data should fail @@ -62,14 +62,14 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy): assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy): +def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, dummyGUI): """Check that new root folders can be added to the project. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "2_nwProject.nwx") refFile = os.path.join(nwRef, "proj", "2_nwProject.nwx") - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.newProject({"projPath": nwFuncTemp}) @@ -96,14 +96,14 @@ def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy): assert not theProject.projChanged @pytest.mark.project -def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy): +def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, dummyGUI): """Check that new files can be added to the project. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "3_nwProject.nwx") refFile = os.path.join(nwRef, "proj", "3_nwProject.nwx") - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.newProject({"projPath": nwFuncTemp}) @@ -123,7 +123,7 @@ def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy): assert not theProject.projChanged @pytest.mark.project -def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, nwDummy): +def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, dummyGUI): """Create a new project from a project wizard dictionary. Custom type with chapters and scenes. """ @@ -151,7 +151,7 @@ def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, nwDummy): "numScenes": 3, "chFolders": True, } - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.newProject(projData) @@ -162,7 +162,7 @@ def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, nwDummy): assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, nwDummy): +def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, dummyGUI): """Create a new project from a project wizard dictionary. Custom type without chapters, but with scenes. """ @@ -190,7 +190,7 @@ def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, nwDummy): "numScenes": 6, "chFolders": True, } - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.newProject(projData) @@ -201,7 +201,7 @@ def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, nwDummy): assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewSampleA(nwFuncTemp, nwConf, nwDummy, nwTemp): +def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir): """Check that we can create a new project can be created from the provided sample project via a zip file. """ @@ -214,7 +214,7 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, nwDummy, nwTemp): "popMinimal": False, "popCustom": False, } - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) theProject.mainConf = nwConf @@ -223,8 +223,8 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, nwDummy, nwTemp): # Force the lookup path for assets to our temp folder srcSample = os.path.abspath(os.path.join(nwConf.appRoot, "sample")) - dstSample = os.path.join(nwTemp, "sample.zip") - nwConf.assetPath = nwTemp + dstSample = os.path.join(tmpDir, "sample.zip") + nwConf.assetPath = tmpDir # Create and open a defective zip file with open(dstSample, mode="w+") as outFile: @@ -248,7 +248,7 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, nwDummy, nwTemp): os.unlink(dstSample) @pytest.mark.project -def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, nwDummy, nwTemp): +def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir): """Check that we can create a new project can be created from the provided sample project folder. """ @@ -261,12 +261,12 @@ def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, nwDummy, nwTemp): "popMinimal": False, "popCustom": False, } - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) theProject.mainConf = nwConf # Make sure we do not pick up the nw/assets/sample.zip file - nwConf.assetPath = nwTemp + nwConf.assetPath = tmpDir # Set a fake project file name monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx") @@ -280,14 +280,14 @@ def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, nwDummy, nwTemp): assert theProject.closeProject() # Misdirect the appRoot path so neither is possible - nwConf.appRoot = nwTemp + nwConf.appRoot = tmpDir assert not theProject.newProject(projData) @pytest.mark.project -def testProjectMethods(monkeypatch, nwMinimal, nwDummy): +def testProjectMethods(monkeypatch, nwMinimal, dummyGUI): """Test other project class methods and functions. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwMinimal) assert theProject.projPath == nwMinimal @@ -326,14 +326,14 @@ def testProjectMethods(monkeypatch, nwMinimal, nwDummy): assert theProject.bookAuthors == ["Jane Doe", "John Doh"] @pytest.mark.project -def testDocMeta(nwDummy, nwLipsum): +def testDocMeta(dummyGUI, nwLipsum): """Check that the document meta data string is parsed correctly. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) assert theProject.openProject(nwLipsum) - aDoc = NWDoc(theProject, nwDummy) + aDoc = NWDoc(theProject, dummyGUI) assert aDoc.openDocument("47666c91c7ccf") theName, theParent, theClass, theLayout = aDoc.getMeta() @@ -350,8 +350,8 @@ def testDocMeta(nwDummy, nwLipsum): assert theLayout is None @pytest.mark.project -def testSpellEnchant(nwTemp, nwConf): - wList = os.path.join(nwTemp, "wordlist.txt") +def testSpellEnchant(tmpDir, nwConf): + wList = os.path.join(tmpDir, "wordlist.txt") with open(wList, mode="w") as wFile: wFile.write("a_word\nb_word\nc_word\n") @@ -379,8 +379,8 @@ def testSpellEnchant(nwTemp, nwConf): assert aName != "" @pytest.mark.project -def testSpellSimple(nwTemp, nwConf): - wList = os.path.join(nwTemp, "wordlist.txt") +def testSpellSimple(tmpDir, nwConf): + wList = os.path.join(tmpDir, "wordlist.txt") with open(wList, mode="w") as wFile: wFile.write("a_word\nb_word\nc_word\n") @@ -408,12 +408,12 @@ def testSpellSimple(nwTemp, nwConf): assert aName == nwConst.SP_INTERNAL @pytest.mark.project -def testProjectOptions(nwDummy, nwLipsum): +def testProjectOptions(dummyGUI, nwLipsum): """Test the class that holds all the GUI state user options that are tied to the current open project. Non-project related GUI options are handled by the Config class. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) assert theProject.projMeta is None theOpts = theProject.optState @@ -471,13 +471,13 @@ def testProjectOptions(nwDummy, nwLipsum): assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False @pytest.mark.project -def testProjectOrphanedFiles(nwDummy, nwLipsum): +def testProjectOrphanedFiles(dummyGUI, nwLipsum): """Check that files in the content folder that are not tracked in the project XML file are handled correctly by the orphaned files function. It should also restore as much meta data as possible from the meta line at the top of the document file. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) assert theProject.openProject(nwLipsum) assert theProject.projTree["636b6aa9b697b"] is None assert theProject.closeProject() @@ -540,13 +540,13 @@ def testProjectOrphanedFiles(nwDummy, nwLipsum): assert theProject.closeProject() @pytest.mark.project -def testProjectOldFormat(nwDummy, nwOldProj): +def testProjectOldFormat(dummyGUI, nwOldProj): """Test that a project folder structure of version 1.0 can be converted to the latest folder structure. Version 1.0 split the documents into 'data_0' ... 'data_f' folders, which are now all contained in a single 'content' folder. """ - theProject = NWProject(nwDummy) + theProject = NWProject(dummyGUI) theProject.mainConf.showGUI = False # Create dummy files for known legacy files @@ -630,13 +630,13 @@ def testProjectOldFormat(nwDummy, nwOldProj): assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt")) @pytest.mark.project -def testProjectBackup(nwDummy, nwMinimal, nwTemp): +def testProjectBackup(dummyGUI, nwMinimal, 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(nwDummy) + theProject = NWProject(dummyGUI) assert theProject.openProject(nwMinimal) # Test faulty settings @@ -645,12 +645,12 @@ def testProjectBackup(nwDummy, nwMinimal, nwTemp): assert not theProject.zipIt(doNotify=False) # Missing project name - theProject.mainConf.backupPath = nwTemp + theProject.mainConf.backupPath = tmpDir theProject.projName = "" assert not theProject.zipIt(doNotify=False) # Non-existent folder - theProject.mainConf.backupPath = os.path.join(nwTemp, "nonexistent") + theProject.mainConf.backupPath = os.path.join(tmpDir, "nonexistent") theProject.projName = "Test Minimal" assert not theProject.zipIt(doNotify=False) @@ -659,10 +659,10 @@ def testProjectBackup(nwDummy, nwMinimal, nwTemp): assert not theProject.zipIt(doNotify=False) # Test correct settings - theProject.mainConf.backupPath = nwTemp + theProject.mainConf.backupPath = tmpDir assert theProject.zipIt(doNotify=False) - theFiles = os.listdir(os.path.join(nwTemp, "Test Minimal")) + theFiles = os.listdir(os.path.join(tmpDir, "Test Minimal")) assert len(theFiles) == 1 theZip = theFiles[0] @@ -670,10 +670,10 @@ def testProjectBackup(nwDummy, nwMinimal, nwTemp): assert theZip[-4:] == ".zip" # Extract the archive - with ZipFile(os.path.join(nwTemp, "Test Minimal", theZip), "r") as inZip: - inZip.extractall(os.path.join(nwTemp, "extract")) + with ZipFile(os.path.join(tmpDir, "Test Minimal", theZip), "r") as inZip: + inZip.extractall(os.path.join(tmpDir, "extract")) # Check that the main project file was restored assert cmpFiles( - os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(nwTemp, "extract", "nwProject.nwx") + os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx") ) From deededb0d9f730385159fb187b5a3a81f4aecd87 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 10:49:06 +0100 Subject: [PATCH 06/22] Updated NWIndex tests --- ...json => coreIndex_LoadSave_tagsIndex.json} | 0 tests/{test_index.py => test_core_index.py} | 95 +++++++++++-------- tests/test_core_tree.py | 4 +- 3 files changed, 58 insertions(+), 41 deletions(-) rename tests/reference/{proj/1_tagsIndex.json => coreIndex_LoadSave_tagsIndex.json} (100%) rename tests/{test_index.py => test_core_index.py} (87%) diff --git a/tests/reference/proj/1_tagsIndex.json b/tests/reference/coreIndex_LoadSave_tagsIndex.json similarity index 100% rename from tests/reference/proj/1_tagsIndex.json rename to tests/reference/coreIndex_LoadSave_tagsIndex.json diff --git a/tests/test_index.py b/tests/test_core_index.py similarity index 87% rename from tests/test_index.py rename to tests/test_core_index.py index dd5add5c..a3bafa2f 100644 --- a/tests/test_index.py +++ b/tests/test_core_index.py @@ -14,14 +14,14 @@ from nw.core.project import NWProject from nw.core.index import NWIndex from nw.constants import nwItemClass, nwItemLayout -@pytest.mark.project -def testIndexBuildCheck(monkeypatch, nwLipsum, dummyGUI, nwTempProj, nwRef): +@pytest.mark.core +def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, nwTempProj, nwRef): """Test core functionality of scaning, saving, loading and checking the index cache file. """ projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") - testFile = os.path.join(nwTempProj, "1_tagsIndex.json") - refFile = os.path.join(nwRef, "proj", "1_tagsIndex.json") + testFile = os.path.join(nwTempProj, "coreIndex_LoadSave_tagsIndex.json") + compFile = os.path.join(nwRef, "coreIndex_LoadSave_tagsIndex.json") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -136,10 +136,12 @@ def testIndexBuildCheck(monkeypatch, nwLipsum, dummyGUI, nwTempProj, nwRef): assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile) + assert cmpFiles(testFile, compFile) -@pytest.mark.project -def testIndexScanThis(nwMinimal, dummyGUI): +# END Test testCoreIndex_LoadSave + +@pytest.mark.core +def testCoreIndex_ScanThis(nwMinimal, dummyGUI): """Test the tag scanner function scanThis. """ theProject = NWProject(dummyGUI) @@ -162,33 +164,35 @@ def testIndexScanThis(nwMinimal, dummyGUI): isValid, theBits, thePos = theIndex.scanThis("@a:") assert isValid - assert str(theBits) == "['@a']" - assert str(thePos) == "[0]" + assert theBits == ["@a"] + assert thePos == [0] isValid, theBits, thePos = theIndex.scanThis("@a:b") assert isValid - assert str(theBits) == "['@a', 'b']" - assert str(thePos) == "[0, 3]" + assert theBits == ["@a", "b"] + assert thePos == [0, 3] isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d") assert isValid - assert str(theBits) == "['@a', 'b', 'c', 'd']" - assert str(thePos) == "[0, 3, 5, 7]" + assert theBits == ["@a", "b", "c", "d"] + assert thePos == [0, 3, 5, 7] isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d") assert isValid - assert str(theBits) == "['@a', 'b', 'c', 'd']" - assert str(thePos) == "[0, 5, 9, 13]" + assert theBits == ["@a", "b", "c", "d"] + assert thePos == [0, 5, 9, 13] isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this") assert isValid - assert str(theBits) == "['@tag', 'this', 'and this']" - assert str(thePos) == "[0, 6, 12]" + assert theBits == ["@tag", "this", "and this"] + assert thePos == [0, 6, 12] assert theProject.closeProject() -@pytest.mark.project -def testIndexCheckThese(nwMinimal, dummyGUI): +# END Test testCoreIndex_ScanThis + +@pytest.mark.core +def testCoreIndex_CheckThese(nwMinimal, dummyGUI): """Test the tag checker function checkThese. """ theProject = NWProject(dummyGUI) @@ -209,23 +213,26 @@ def testIndexCheckThese(nwMinimal, dummyGUI): "# Hello World!\n" "@pov: Jane" )) - assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle + assert theIndex.tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]} assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" - assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]" - assert str(theIndex.checkThese(["@tag", "John"], cItem)) == "[True, True]" - assert str(theIndex.checkThese(["@tag", "Jane"], nItem)) == "[True, False]" - assert str(theIndex.checkThese(["@tag", "John"], nItem)) == "[True, True]" - assert str(theIndex.checkThese(["@pov", "John"], nItem)) == "[True, False]" - assert str(theIndex.checkThese(["@pov", "Jane"], nItem)) == "[True, True]" - assert str(theIndex.checkThese(["@ pov", "Jane"], nItem)) == "[False, False]" - assert str(theIndex.checkThese(["@what", "Jane"], nItem)) == "[False, False]" + assert theIndex.checkThese([], cItem) == [] + assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True] + assert theIndex.checkThese(["@tag", "John"], cItem) == [True, True] + assert theIndex.checkThese(["@tag", "Jane"], nItem) == [True, False] + assert theIndex.checkThese(["@tag", "John"], nItem) == [True, True] + assert theIndex.checkThese(["@pov", "John"], nItem) == [True, False] + assert theIndex.checkThese(["@pov", "Jane"], nItem) == [True, True] + assert theIndex.checkThese(["@ pov", "Jane"], nItem) == [False, False] + assert theIndex.checkThese(["@what", "Jane"], nItem) == [False, False] assert theProject.closeProject() -@pytest.mark.project -def testIndexScanText(nwMinimal, dummyGUI): - """Check the index data extraction functions. +# END Test testCoreIndex_CheckThese + +@pytest.mark.core +def testCoreIndex_ScanText(nwMinimal, dummyGUI): + """Check the index text scanner. """ theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -392,14 +399,16 @@ def testIndexScanText(nwMinimal, dummyGUI): "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" )) - assert str(theIndex.refIndex[sHandle]["T000001"]["tags"]) == ( - "[[3, '@pov', 'One'], [5, '@char', 'Two']]" + assert theIndex.refIndex[sHandle]["T000001"]["tags"] == ( + [[3, "@pov", "One"], [5, "@char", "Two"]] ) assert theProject.closeProject() -@pytest.mark.project -def testIndexExtractData(nwMinimal, dummyGUI): +# END Test testCoreIndex_ScanText + +@pytest.mark.core +def testCoreIndex_ExtractData(nwMinimal, dummyGUI): """Check the index data extraction functions. """ theProject = NWProject(dummyGUI) @@ -424,7 +433,13 @@ def testIndexExtractData(nwMinimal, dummyGUI): )) # The novel structure should contain the pointer to the novel file header - assert str(theIndex.getNovelStructure()) == "['%s:T000001']" % nHandle + assert theIndex.getNovelStructure() == ["%s:T000001" % nHandle] + + # Check that excluded files can be skipped + theProject.projTree[nHandle].setExported(False) + assert theIndex.getNovelStructure(skipExcluded=False) == ["%s:T000001" % nHandle] + assert theIndex.getNovelStructure(skipExcluded=True) == [] + assert theIndex.getNovelStructure() == [] # The novel file should have the correct counts cC, wC, pC = theIndex.getCounts(nHandle) @@ -443,8 +458,8 @@ def testIndexExtractData(nwMinimal, dummyGUI): # The novel file should now refer to Jane as @pov and @char theRefs = theIndex.getReferences(nHandle) - assert str(theRefs["@pov"]) == "['Jane']" - assert str(theRefs["@char"]) == "['Jane']" + assert theRefs["@pov"] == ["Jane"] + assert theRefs["@char"] == ["Jane"] ## # getBackReferenceList @@ -455,7 +470,7 @@ def testIndexExtractData(nwMinimal, dummyGUI): # The character file should have a record of the reference from the novel file theRefs = theIndex.getBackReferenceList(cHandle) - assert str(theRefs) == "{'%s': 'T000001'}" % nHandle + assert theRefs == {nHandle: "T000001"} ## # getTagSource @@ -535,3 +550,5 @@ def testIndexExtractData(nwMinimal, dummyGUI): assert pC == 2 assert theProject.closeProject() + +# END Test testCoreIndex_ExtractData diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py index c3b68964..447ff1cc 100644 --- a/tests/test_core_tree.py +++ b/tests/test_core_tree.py @@ -239,7 +239,7 @@ def testCoreTree_Methods(dummyGUI, dummyItems): # END Test testCoreTree_Methods @pytest.mark.core -def testCoreTree_MakeHandles(dummyGUI, monkeypatch): +def testCoreTree_MakeHandles(monkeypatch, dummyGUI): """Test generating item handles. """ theProject = NWProject(dummyGUI) @@ -384,7 +384,7 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems): # END Test testCoreTree_XMLPackUnpack @pytest.mark.core -def testCoreTree_ToCFile(dummyGUI, dummyItems, tmpDir, monkeypatch): +def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir): """Test writing the ToC.txt file. """ theProject = NWProject(dummyGUI) From afea7b66bde8e9a5236747424afe7ce0bdfecfd2 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 10:50:41 +0100 Subject: [PATCH 07/22] Rename nwRef to refDir --- tests/conftest.py | 26 ++++++++----------- tests/test_config.py | 22 ++++++++-------- tests/test_core_index.py | 4 +-- tests/test_dialogs.py | 56 ++++++++++++++++++++-------------------- tests/test_gui.py | 14 +++++----- tests/test_project.py | 20 +++++++------- 6 files changed, 69 insertions(+), 73 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 863818eb..2d2ec6e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,15 @@ def tmpDir(): os.mkdir(tempDir) return tempDir +@pytest.fixture(scope="session") +def refDir(): + """The folder where all the reference files are stored for verifying + the results of tests. + """ + testDir = os.path.dirname(__file__) + refDir = os.path.join(testDir, "reference") + return refDir + ## # novelWriter Objects ## @@ -56,30 +65,17 @@ def dummyGUI(tmpConf): # =============================================================================================== # -## -# Core Test Folders -## - -@pytest.fixture(scope="session") -def nwRef(): - """The folder where all the reference files are stored for verifying - the results of tests. - """ - testDir = os.path.dirname(__file__) - refDir = os.path.join(testDir, "reference") - return refDir - ## # novelWriter Objects ## @pytest.fixture(scope="session") -def nwConf(nwRef, tmpDir): +def nwConf(refDir, tmpDir): """Temporary novelWriter configuration used for the dummy instance of novelWriter's main GUI. """ theConf = Config() - theConf.initConfig(nwRef, tmpDir) + theConf.initConfig(refDir, tmpDir) return theConf ## diff --git a/tests/test_config.py b/tests/test_config.py index ad83ddd5..c23f6bc6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,8 +8,8 @@ import os from nwtools import cmpFiles @pytest.mark.core -def testConfigCore(tmpConf, tmpDir, nwRef): - refConf = os.path.join(nwRef, "novelwriter.conf") +def testConfigCore(tmpConf, tmpDir, refDir): + refConf = os.path.join(refDir, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") assert tmpConf.confPath == tmpDir @@ -38,8 +38,8 @@ def testConfigSetDataPath(tmpConf, tmpDir): assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetWinSize(tmpConf, tmpDir, nwRef): - refConf = os.path.join(nwRef, "novelwriter.conf") +def testConfigSetWinSize(tmpConf, tmpDir, refDir): + refConf = os.path.join(refDir, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") tmpConf.guiScale = 1.0 @@ -55,8 +55,8 @@ def testConfigSetWinSize(tmpConf, tmpDir, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetTreeColWidths(tmpConf, tmpDir, nwRef): - refConf = os.path.join(nwRef, "novelwriter.conf") +def testConfigSetTreeColWidths(tmpConf, tmpDir, refDir): + refConf = os.path.join(refDir, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") assert tmpConf.confPath == tmpDir @@ -77,8 +77,8 @@ def testConfigSetTreeColWidths(tmpConf, tmpDir, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigSetPanePos(tmpConf, tmpDir, nwRef): - refConf = os.path.join(nwRef, "novelwriter.conf") +def testConfigSetPanePos(tmpConf, tmpDir, refDir): + refConf = os.path.join(refDir, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") assert tmpConf.confPath == tmpDir @@ -113,8 +113,8 @@ def testConfigSetPanePos(tmpConf, tmpDir, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testConfigFlags(tmpConf, tmpDir, nwRef): - refConf = os.path.join(nwRef, "novelwriter.conf") +def testConfigFlags(tmpConf, tmpDir, refDir): + refConf = os.path.join(refDir, "novelwriter.conf") testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") assert tmpConf.confPath == tmpDir @@ -137,7 +137,7 @@ def testConfigFlags(tmpConf, tmpDir, nwRef): assert not tmpConf.confChanged @pytest.mark.core -def testTextSizes(tmpConf, tmpDir, nwRef): +def testTextSizes(tmpConf, tmpDir, refDir): assert tmpConf.confPath == tmpDir tmpConf.guiScale = 2.0 diff --git a/tests/test_core_index.py b/tests/test_core_index.py index a3bafa2f..74bdd899 100644 --- a/tests/test_core_index.py +++ b/tests/test_core_index.py @@ -15,13 +15,13 @@ from nw.core.index import NWIndex from nw.constants import nwItemClass, nwItemLayout @pytest.mark.core -def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, nwTempProj, nwRef): +def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, nwTempProj, refDir): """Test core functionality of scaning, saving, loading and checking the index cache file. """ projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") testFile = os.path.join(nwTempProj, "coreIndex_LoadSave_tagsIndex.json") - compFile = os.path.join(nwRef, "coreIndex_LoadSave_tagsIndex.json") + compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index 5e9a319f..7906c8e8 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -30,7 +30,7 @@ typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwRef, tmpDir): +def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -137,7 +137,7 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR # Check the files projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "2_nwProject.nwx") - refFile = os.path.join(nwRef, "gui", "2_nwProject.nwx") + refFile = os.path.join(refDir, "gui", "2_nwProject.nwx") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 8, 9, 10]) @@ -145,7 +145,7 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR nwGUI.closeMain() @pytest.mark.gui -def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, tmpDir): +def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -210,7 +210,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, t # Check the files projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "3_nwProject.nwx") - refFile = os.path.join(nwRef, "gui", "3_nwProject.nwx") + refFile = os.path.join(refDir, "gui", "3_nwProject.nwx") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @@ -397,7 +397,7 @@ def testAboutBox(qtbot, monkeypatch, nwFuncTemp, tmpDir): nwGUI.closeMain() @pytest.mark.gui -def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): +def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) @@ -428,13 +428,13 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd") testFile = os.path.join(nwTempBuild, "1_LoremIpsum.nwd") - refFile = os.path.join(nwRef, "build", "1_LoremIpsum.nwd") + refFile = os.path.join(refDir, "build", "1_LoremIpsum.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm") testFile = os.path.join(nwTempBuild, "1_LoremIpsum.htm") - refFile = os.path.join(nwRef, "build", "1_LoremIpsum.htm") + refFile = os.path.join(refDir, "build", "1_LoremIpsum.htm") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -467,13 +467,13 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd") testFile = os.path.join(nwTempBuild, "2_LoremIpsum.nwd") - refFile = os.path.join(nwRef, "build", "2_LoremIpsum.nwd") + refFile = os.path.join(refDir, "build", "2_LoremIpsum.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm") testFile = os.path.join(nwTempBuild, "2_LoremIpsum.htm") - refFile = os.path.join(nwRef, "build", "2_LoremIpsum.htm") + refFile = os.path.join(refDir, "build", "2_LoremIpsum.htm") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -487,14 +487,14 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): assert nwBuild._saveDocument(nwBuild.FMT_NWD) projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd") testFile = os.path.join(nwTempBuild, "3_LoremIpsum.nwd") - refFile = os.path.join(nwRef, "build", "3_LoremIpsum.nwd") + refFile = os.path.join(refDir, "build", "3_LoremIpsum.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) assert nwBuild._saveDocument(nwBuild.FMT_HTM) projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm") testFile = os.path.join(nwTempBuild, "3_LoremIpsum.htm") - refFile = os.path.join(nwRef, "build", "3_LoremIpsum.htm") + refFile = os.path.join(refDir, "build", "3_LoremIpsum.htm") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -521,14 +521,14 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): assert nwBuild._saveDocument(nwBuild.FMT_NWD) projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd") testFile = os.path.join(nwTempBuild, "4_LoremIpsum.nwd") - refFile = os.path.join(nwRef, "build", "4_LoremIpsum.nwd") + refFile = os.path.join(refDir, "build", "4_LoremIpsum.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) assert nwBuild._saveDocument(nwBuild.FMT_HTM) projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm") testFile = os.path.join(nwTempBuild, "4_LoremIpsum.htm") - refFile = os.path.join(nwRef, "build", "4_LoremIpsum.htm") + refFile = os.path.join(refDir, "build", "4_LoremIpsum.htm") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -536,14 +536,14 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): assert nwBuild._saveDocument(nwBuild.FMT_JSON_H) projFile = os.path.join(nwLipsum, "Lorem Ipsum.json") testFile = os.path.join(nwTempBuild, "4H_LoremIpsum.json") - refFile = os.path.join(nwRef, "build", "4H_LoremIpsum.json") + refFile = os.path.join(refDir, "build", "4H_LoremIpsum.json") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [8]) assert nwBuild._saveDocument(nwBuild.FMT_JSON_M) projFile = os.path.join(nwLipsum, "Lorem Ipsum.json") testFile = os.path.join(nwTempBuild, "4M_LoremIpsum.json") - refFile = os.path.join(nwRef, "build", "4M_LoremIpsum.json") + refFile = os.path.join(refDir, "build", "4M_LoremIpsum.json") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [8]) @@ -589,7 +589,7 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, tmpDir): nwGUI.closeMain() @pytest.mark.gui -def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef, tmpDir): +def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) @@ -620,7 +620,7 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef projFile = os.path.join(nwLipsum, "content", "73475cb40a568.nwd") testFile = os.path.join(nwTempGUI, "4_73475cb40a568.nwd") - refFile = os.path.join(nwRef, "gui", "4_73475cb40a568.nwd") + refFile = os.path.join(refDir, "gui", "4_73475cb40a568.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -646,7 +646,7 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef # This should give us back the file as it was before projFile = os.path.join(nwLipsum, "content", "71ee45a3c0db9.nwd") testFile = os.path.join(nwTempGUI, "4_71ee45a3c0db9.nwd") - refFile = os.path.join(nwRef, "gui", "4_73475cb40a568.nwd") + refFile = os.path.join(refDir, "gui", "4_73475cb40a568.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [1, 2, 3]) @@ -670,19 +670,19 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef projFile = os.path.join(nwLipsum, "content", "25fc0e7096fc6.nwd") testFile = os.path.join(nwTempGUI, "5_25fc0e7096fc6.nwd") - refFile = os.path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd") + refFile = os.path.join(refDir, "gui", "5_25fc0e7096fc6.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "content", "31489056e0916.nwd") testFile = os.path.join(nwTempGUI, "5_31489056e0916.nwd") - refFile = os.path.join(nwRef, "gui", "5_31489056e0916.nwd") + refFile = os.path.join(refDir, "gui", "5_31489056e0916.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "content", "98010bd9270f9.nwd") testFile = os.path.join(nwTempGUI, "5_98010bd9270f9.nwd") - refFile = os.path.join(nwRef, "gui", "5_98010bd9270f9.nwd") + refFile = os.path.join(refDir, "gui", "5_98010bd9270f9.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -708,31 +708,31 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef projFile = os.path.join(nwLipsum, "content", "1a6562590ef19.nwd") testFile = os.path.join(nwTempGUI, "5_25fc0e7096fc6.nwd") - refFile = os.path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd") + refFile = os.path.join(refDir, "gui", "5_25fc0e7096fc6.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [1, 2, 3]) projFile = os.path.join(nwLipsum, "content", "031b4af5197ec.nwd") testFile = os.path.join(nwTempGUI, "5_031b4af5197ec.nwd") - refFile = os.path.join(nwRef, "gui", "5_031b4af5197ec.nwd") + refFile = os.path.join(refDir, "gui", "5_031b4af5197ec.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd") testFile = os.path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd") - refFile = os.path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd") + refFile = os.path.join(refDir, "gui", "5_41cfc0d1f2d12.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "content", "2858dcd1057d3.nwd") testFile = os.path.join(nwTempGUI, "5_2858dcd1057d3.nwd") - refFile = os.path.join(nwRef, "gui", "5_2858dcd1057d3.nwd") + refFile = os.path.join(refDir, "gui", "5_2858dcd1057d3.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwLipsum, "content", "2fca346db6561.nwd") testFile = os.path.join(nwTempGUI, "5_2fca346db6561.nwd") - refFile = os.path.join(nwRef, "gui", "5_2fca346db6561.nwd") + refFile = os.path.join(refDir, "gui", "5_2fca346db6561.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) @@ -1017,7 +1017,7 @@ def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): nwGUI.close() @pytest.mark.gui -def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir, nwRef, tmpConf): +def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir, refDir, tmpConf): nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -1203,7 +1203,7 @@ def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir, nwRef, tmpC # qtbot.stopForInteraction() nwGUI.closeMain() - refConf = os.path.join(nwRef, "novelwriter_prefs.conf") + refConf = os.path.join(refDir, "novelwriter_prefs.conf") projConf = os.path.join(nwGUI.mainConf.confPath, "novelwriter.conf") testConf = os.path.join(tmpDir, "novelwriter_prefs.conf") copyfile(projConf, testConf) diff --git a/tests/test_gui.py b/tests/test_gui.py index 2b10b5de..014d8f34 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -103,7 +103,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): monkeypatch.undo() @pytest.mark.gui -def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, tmpDir): +def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) @@ -132,7 +132,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, tmpDir): # Check the files projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "0_nwProject.nwx") - refFile = os.path.join(nwRef, "gui", "0_nwProject.nwx") + refFile = os.path.join(refDir, "gui", "0_nwProject.nwx") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) qtbot.wait(stepDelay) @@ -381,31 +381,31 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, tmpDir): # Check the files projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "1_nwProject.nwx") - refFile = os.path.join(nwRef, "gui", "1_nwProject.nwx") + refFile = os.path.join(refDir, "gui", "1_nwProject.nwx") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) projFile = os.path.join(nwFuncTemp, "content", "031b4af5197ec.nwd") testFile = os.path.join(nwTempGUI, "1_031b4af5197ec.nwd") - refFile = os.path.join(nwRef, "gui", "1_031b4af5197ec.nwd") + refFile = os.path.join(refDir, "gui", "1_031b4af5197ec.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwFuncTemp, "content", "1a6562590ef19.nwd") testFile = os.path.join(nwTempGUI, "1_1a6562590ef19.nwd") - refFile = os.path.join(nwRef, "gui", "1_1a6562590ef19.nwd") + refFile = os.path.join(refDir, "gui", "1_1a6562590ef19.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd") testFile = os.path.join(nwTempGUI, "1_0e17daca5f3e1.nwd") - refFile = os.path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd") + refFile = os.path.join(refDir, "gui", "1_0e17daca5f3e1.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) projFile = os.path.join(nwFuncTemp, "content", "41cfc0d1f2d12.nwd") testFile = os.path.join(nwTempGUI, "1_41cfc0d1f2d12.nwd") - refFile = os.path.join(nwRef, "gui", "1_41cfc0d1f2d12.nwd") + refFile = os.path.join(refDir, "gui", "1_41cfc0d1f2d12.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) diff --git a/tests/test_project.py b/tests/test_project.py index b8a49aa9..0ca04f91 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -16,12 +16,12 @@ from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple from nw.constants import nwConst, nwItemClass, nwItemType, nwItemLayout, nwFiles @pytest.mark.project -def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, tmpDir, dummyGUI): +def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI): """Test that a basic project can be created, and opened and saved. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "1_nwProject.nwx") - refFile = os.path.join(nwRef, "proj", "1_nwProject.nwx") + refFile = os.path.join(refDir, "proj", "1_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -62,12 +62,12 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, tmpDir, dummyGUI): assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, dummyGUI): +def testProjectNewRoot(nwFuncTemp, nwTempProj, refDir, dummyGUI): """Check that new root folders can be added to the project. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "2_nwProject.nwx") - refFile = os.path.join(nwRef, "proj", "2_nwProject.nwx") + refFile = os.path.join(refDir, "proj", "2_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -96,12 +96,12 @@ def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, dummyGUI): assert not theProject.projChanged @pytest.mark.project -def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, dummyGUI): +def testProjectNewFile(nwFuncTemp, nwTempProj, refDir, dummyGUI): """Check that new files can be added to the project. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "3_nwProject.nwx") - refFile = os.path.join(nwRef, "proj", "3_nwProject.nwx") + refFile = os.path.join(refDir, "proj", "3_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -123,13 +123,13 @@ def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, dummyGUI): assert not theProject.projChanged @pytest.mark.project -def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, dummyGUI): +def testProjectNewCustomA(nwFuncTemp, nwTempProj, refDir, dummyGUI): """Create a new project from a project wizard dictionary. Custom type with chapters and scenes. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "4_nwProject.nwx") - refFile = os.path.join(nwRef, "proj", "4_nwProject.nwx") + refFile = os.path.join(refDir, "proj", "4_nwProject.nwx") projData = { "projName": "Test Custom", @@ -162,13 +162,13 @@ def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, dummyGUI): assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) @pytest.mark.project -def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, dummyGUI): +def testProjectNewCustomB(nwFuncTemp, nwTempProj, refDir, dummyGUI): """Create a new project from a project wizard dictionary. Custom type without chapters, but with scenes. """ projFile = os.path.join(nwFuncTemp, "nwProject.nwx") testFile = os.path.join(nwTempProj, "5_nwProject.nwx") - refFile = os.path.join(nwRef, "proj", "5_nwProject.nwx") + refFile = os.path.join(refDir, "proj", "5_nwProject.nwx") projData = { "projName": "Test Custom", From 2018d44ba85b537000e054783b0bc0989f073cef Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 21:00:31 +0100 Subject: [PATCH 08/22] Some changes to fixtures and added tests README --- tests/README.md | 68 ++++++++++++++++++++++++++++++++++++++++ tests/conftest.py | 25 ++++++++++----- tests/test_core_index.py | 4 +-- 3 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..6a2a792b --- /dev/null +++ b/tests/README.md @@ -0,0 +1,68 @@ +# novelWriter Tests + +The test suite uses PyTest for testing. + +## Dependencies + +* `python3-pytest` for the basic framework (required) +* `python3-pytestqt` for Qt support (required) +* `python3-pytest-cov` for code coverage reports (optional) +* `python3-pytest-xvfb` for headless tests (optional) + +## HowTo + +### Basic Usage + +To run all tests, type: +```bash +pytest-3 -v +``` + +The `-v` switch enables verbose mode, with one test per line. +For a more compact view, omit this switch. + +### Headless + +To run tests in headless mode, either use the Qt `offscreen` mode: +```bash +export QT_QPA_PLATFORM=offscreen +``` + +or run with `xvfb`: +```bash +xvfb-run pytest-3 -v +``` + +### Test Coverage + +To add test coverage, run the following: +```bash +pytest-3 -v --cov=nw --cov-report=html +``` + +The `--cov-report` switch generates an html report, omit it to print a coverage summary to the terminal. +The html coverage report will be available in the `htmlcov` folder. + +### Test Markers (Categories) + +To run with specific test markers, add the `-m` switch: +```bash +pytest-3 -v -m core +``` + +Available markers are: + +* '`core`' for test covering the classes in the `nw/core` folder + +## Tests + +To filter specific groups of tests, use the `-k` switch. +The commands for the respective test categories are listed below. + +| Type | Test Target | Source File(s) | Marker | Filter | +| :--- | :------------- | :--------------- | :-------- | :----------------- | +| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | +| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | +| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | +| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | + diff --git a/tests/conftest.py b/tests/conftest.py index 2d2ec6e8..0e59a3dc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,12 +26,12 @@ def tmpDir(): be checked. The folder is instead cleared before a new test session. """ testDir = os.path.dirname(__file__) - tempDir = os.path.join(testDir, "temp") - if os.path.isdir(tempDir): - shutil.rmtree(tempDir) - if not os.path.isdir(tempDir): - os.mkdir(tempDir) - return tempDir + theDir = os.path.join(testDir, "temp") + if os.path.isdir(theDir): + shutil.rmtree(theDir) + if not os.path.isdir(theDir): + os.mkdir(theDir) + return theDir @pytest.fixture(scope="session") def refDir(): @@ -39,8 +39,17 @@ def refDir(): the results of tests. """ testDir = os.path.dirname(__file__) - refDir = os.path.join(testDir, "reference") - return refDir + theDir = os.path.join(testDir, "reference") + return theDir + +@pytest.fixture(scope="session") +def outDir(tmpDir): + """An output folder for test results + """ + theDir = os.path.join(tmpDir, "results") + if not os.path.isdir(theDir): + os.mkdir(theDir) + return theDir ## # novelWriter Objects diff --git a/tests/test_core_index.py b/tests/test_core_index.py index 74bdd899..1d577b70 100644 --- a/tests/test_core_index.py +++ b/tests/test_core_index.py @@ -15,12 +15,12 @@ from nw.core.index import NWIndex from nw.constants import nwItemClass, nwItemLayout @pytest.mark.core -def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, nwTempProj, refDir): +def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir): """Test core functionality of scaning, saving, loading and checking the index cache file. """ projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") - testFile = os.path.join(nwTempProj, "coreIndex_LoadSave_tagsIndex.json") + testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json") compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") theProject = NWProject(dummyGUI) From 8cc6fa60305fcddf87c34badee10ab4766da11e6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:19:18 +0100 Subject: [PATCH 09/22] New test for NWDoc class --- nw/core/document.py | 3 +- tests/README.md | 13 ++-- tests/conftest.py | 122 +++++++++++++++--------------- tests/test_core_document.py | 146 ++++++++++++++++++++++++++++++++++++ tests/test_core_item.py | 3 +- tests/test_project.py | 24 ------ 6 files changed, 218 insertions(+), 93 deletions(-) create mode 100644 tests/test_core_document.py diff --git a/nw/core/document.py b/nw/core/document.py index 2597eac8..7ae8f4de 100644 --- a/nw/core/document.py +++ b/nw/core/document.py @@ -168,7 +168,8 @@ class NWDoc(): os.unlink(docPath) os.rename(docTemp, docPath) - self.theParent.setStatus("Saved Document: %s" % self._theItem.itemName) + if self._theItem is not None: + self.theParent.setStatus("Saved Document: %s" % self._theItem.itemName) return True diff --git a/tests/README.md b/tests/README.md index 6a2a792b..7f9e737c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,10 +59,11 @@ Available markers are: To filter specific groups of tests, use the `-k` switch. The commands for the respective test categories are listed below. -| Type | Test Target | Source File(s) | Marker | Filter | -| :--- | :------------- | :--------------- | :-------- | :----------------- | -| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | -| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | -| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | -| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | +| Type | Test Target | Source File(s) | Marker | Filter | +| :--- | :------------- | :------------------ | :-------- | :-------------------- | +| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | +| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | +| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | +| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | +| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | diff --git a/tests/conftest.py b/tests/conftest.py index 0e59a3dc..3eeb8344 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -72,68 +72,8 @@ def dummyGUI(tmpConf): theDummy.mainConf = tmpConf return theDummy -# =============================================================================================== # - ## -# novelWriter Objects -## - -@pytest.fixture(scope="session") -def nwConf(refDir, tmpDir): - """Temporary novelWriter configuration used for the dummy instance - of novelWriter's main GUI. - """ - theConf = Config() - theConf.initConfig(refDir, tmpDir) - return theConf - -## -# Temporary Test Folders -## - -@pytest.fixture(scope="session") -def nwTempProj(tmpDir): - """A temporary folder for project tests. - """ - projDir = os.path.join(tmpDir, "proj") - if not os.path.isdir(projDir): - os.mkdir(projDir) - return projDir - -@pytest.fixture(scope="session") -def nwTempGUI(tmpDir): - """A temporary folder for GUI tests. - """ - guiDir = os.path.join(tmpDir, "gui") - if not os.path.isdir(guiDir): - os.mkdir(guiDir) - return guiDir - -@pytest.fixture(scope="session") -def nwTempBuild(tmpDir): - """A temporary folder for build tests. - """ - buildDir = os.path.join(tmpDir, "build") - if not os.path.isdir(buildDir): - os.mkdir(buildDir) - return buildDir - -@pytest.fixture(scope="function") -def nwFuncTemp(tmpDir): - """A temporary folder for a single test function. - """ - funcDir = os.path.join(tmpDir, "ftemp") - if os.path.isdir(funcDir): - shutil.rmtree(funcDir) - if not os.path.isdir(funcDir): - os.mkdir(funcDir) - yield funcDir - if os.path.isdir(funcDir): - shutil.rmtree(funcDir) - return - -## -# Temp Folders for Projects +# Temp Project Folders ## @pytest.fixture(scope="function") @@ -215,3 +155,63 @@ def yesToAll(monkeypatch): QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes ) return + +# =============================================================================================== # + +## +# novelWriter Objects +## + +@pytest.fixture(scope="session") +def nwConf(refDir, tmpDir): + """Temporary novelWriter configuration used for the dummy instance + of novelWriter's main GUI. + """ + theConf = Config() + theConf.initConfig(refDir, tmpDir) + return theConf + +## +# Temporary Test Folders +## + +@pytest.fixture(scope="session") +def nwTempProj(tmpDir): + """A temporary folder for project tests. + """ + projDir = os.path.join(tmpDir, "proj") + if not os.path.isdir(projDir): + os.mkdir(projDir) + return projDir + +@pytest.fixture(scope="session") +def nwTempGUI(tmpDir): + """A temporary folder for GUI tests. + """ + guiDir = os.path.join(tmpDir, "gui") + if not os.path.isdir(guiDir): + os.mkdir(guiDir) + return guiDir + +@pytest.fixture(scope="session") +def nwTempBuild(tmpDir): + """A temporary folder for build tests. + """ + buildDir = os.path.join(tmpDir, "build") + if not os.path.isdir(buildDir): + os.mkdir(buildDir) + return buildDir + +@pytest.fixture(scope="function") +def nwFuncTemp(tmpDir): + """A temporary folder for a single test function. + """ + funcDir = os.path.join(tmpDir, "ftemp") + if os.path.isdir(funcDir): + shutil.rmtree(funcDir) + if not os.path.isdir(funcDir): + os.mkdir(funcDir) + yield funcDir + if os.path.isdir(funcDir): + shutil.rmtree(funcDir) + return diff --git a/tests/test_core_document.py b/tests/test_core_document.py new file mode 100644 index 00000000..904c9d25 --- /dev/null +++ b/tests/test_core_document.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +"""novelWriter NWDoc Class Tester +""" + +import os +import pytest + +from nw.core import NWProject, NWDoc +from nw.core.item import NWItem +from nw.constants import nwItemClass, nwItemLayout + +@pytest.mark.core +def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal): + """Test loading and saving a document with the NWDoc class. + """ + theProject = NWProject(dummyGUI) + assert theProject.openProject(nwMinimal) + assert theProject.projPath == nwMinimal + + theDoc = NWDoc(theProject, dummyGUI) + sHandle = "8c659a11cd429" + + # Not a valid handle + assert theDoc.openDocument("dummy") is None + + # Non-existent handle + assert theDoc.openDocument("0000000000000") is None + + # Cause open() to fail while loading + def dummyOpen(*args, **kwargs): + raise OSError + + monkeypatch.setattr("builtins.open", dummyOpen) + assert theDoc.openDocument(sHandle) is None + monkeypatch.undo() + + # Load the text + assert theDoc.openDocument(sHandle) == "### New Scene\n\n" + + # Try to open a new (non-existent) file + nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL) + assert nHandle is not None + xHandle = theProject.newFile("New File", nwItemClass.NOVEL, nHandle) + assert theDoc.openDocument(xHandle) == "" + + # Check cached item + assert isinstance(theDoc._theItem, NWItem) + assert theDoc.openDocument(xHandle, isOrphan=True) == "" + assert theDoc._theItem is None + + # Set handle and save again + theText = "### Test File\n\nText ...\n\n" + assert theDoc.openDocument(xHandle) == "" + assert theDoc.saveDocument(theText) + + # Save again to ensure temp file and previous file is handled + assert theDoc.saveDocument(theText) + + # Check file content + docPath = os.path.join(nwMinimal, "content", xHandle+".nwd") + with open(docPath, mode="r", encoding="utf8") as inFile: + assert inFile.read() == ( + "%%~name: New File\n" + f"%%~path: a508bb932959c/{xHandle}\n" + "%%~kind: NOVEL/SCENE\n" + "### Test File\n\n" + "Text ...\n\n" + ) + + # Force no meta data + theDoc._theItem = None + assert theDoc.saveDocument(theText) + + with open(docPath, mode="r", encoding="utf8") as inFile: + assert inFile.read() == theText + + # Cause open() to fail while saving + def dummyIO(*args, **kwargs): + raise OSError + + monkeypatch.setattr("builtins.open", dummyIO) + assert not theDoc.saveDocument(theText) + monkeypatch.undo() + + # Saving with no handle + theDoc.clearDocument() + assert not theDoc.saveDocument(theText) + + # Delete the last document + assert not theDoc.deleteDocument("dummy") + assert os.path.isfile(docPath) + + # Cause the delete to fail + monkeypatch.setattr("os.unlink", dummyIO) + assert not theDoc.deleteDocument(xHandle) + monkeypatch.undo() + + # Make the delete pass + assert theDoc.deleteDocument(xHandle) + assert not os.path.isfile(docPath) + +# END Test testCoreDocument_Load + +@pytest.mark.core +def testCoreDocument_Methods(monkeypatch, dummyGUI, nwMinimal): + """Test other methods of the NWDoc class. + """ + theProject = NWProject(dummyGUI) + assert theProject.openProject(nwMinimal) + assert theProject.projPath == nwMinimal + + theDoc = NWDoc(theProject, dummyGUI) + sHandle = "8c659a11cd429" + docPath = os.path.join(nwMinimal, "content", sHandle+".nwd") + + assert theDoc.openDocument(sHandle) == "### New Scene\n\n" + + # Check location + assert theDoc.getFileLocation() == docPath + + # Check the item + assert theDoc.getCurrentItem() is not None + assert theDoc.getCurrentItem().itemHandle == sHandle + + # Check the meta + theName, theParent, theClass, theLayout = theDoc.getMeta() + assert theName == "New Scene" + assert theParent == "a6d311a93600a" + assert theClass == nwItemClass.NOVEL + assert theLayout == nwItemLayout.SCENE + + # Add meta data garbage + assert theDoc.saveDocument("%%~ stuff\n### Test File\n\nText ...\n\n") + with open(docPath, mode="r", encoding="utf8") as inFile: + assert inFile.read() == ( + "%%~name: New Scene\n" + f"%%~path: a6d311a93600a/{sHandle}\n" + "%%~kind: NOVEL/SCENE\n" + "%%~ stuff\n" + "### Test File\n\n" + "Text ...\n\n" + ) + + assert theDoc.openDocument(sHandle) == "### Test File\n\nText ...\n\n" + +# END Test testCoreDocument_Methods diff --git a/tests/test_core_item.py b/tests/test_core_item.py index e1457792..d592f7c0 100644 --- a/tests/test_core_item.py +++ b/tests/test_core_item.py @@ -6,7 +6,8 @@ import pytest from lxml import etree -from nw.core.project import NWProject, NWItem +from nw.core import NWProject +from nw.core.item import NWItem from nw.constants import nwItemClass, nwItemType, nwItemLayout @pytest.mark.core diff --git a/tests/test_project.py b/tests/test_project.py index 0ca04f91..75c72696 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -325,30 +325,6 @@ def testProjectMethods(monkeypatch, nwMinimal, dummyGUI): assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.bookAuthors == ["Jane Doe", "John Doh"] -@pytest.mark.project -def testDocMeta(dummyGUI, nwLipsum): - """Check that the document meta data string is parsed correctly. - """ - theProject = NWProject(dummyGUI) - theProject.projTree.setSeed(42) - assert theProject.openProject(nwLipsum) - - aDoc = NWDoc(theProject, dummyGUI) - assert aDoc.openDocument("47666c91c7ccf") - theName, theParent, theClass, theLayout = aDoc.getMeta() - - assert theName == "Scene Five" - assert theParent == "6bd935d2490cd" - assert theClass == nwItemClass.NOVEL - assert theLayout == nwItemLayout.SCENE - - aDoc._docMeta = {"stuff": None} - theName, theParent, theClass, theLayout = aDoc.getMeta() - assert theName == "" - assert theParent is None - assert theClass is None - assert theLayout is None - @pytest.mark.project def testSpellEnchant(tmpDir, nwConf): wList = os.path.join(tmpDir, "wordlist.txt") From 7ec43e42fd83ad2188b06a5d9f29b3fe6113a612 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:32:48 +0100 Subject: [PATCH 10/22] Fix flake8 and windows test --- tests/test_core_tree.py | 10 +++++++--- tests/test_project.py | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py index 447ff1cc..f417fd6a 100644 --- a/tests/test_core_tree.py +++ b/tests/test_core_tree.py @@ -413,6 +413,10 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir): theProject.projPath = tmpDir assert theTree.writeToCFile() + pathA = os.path.join("content", "c000000000001.nwd") + pathB = os.path.join("content", "c000000000002.nwd") + pathC = os.path.join("content", "b000000000002.nwd") + with open(os.path.join(tmpDir, nwFiles.TOC_TXT), mode="r", encoding="utf8") as inFile: assert inFile.read() == ( "\n" @@ -421,9 +425,9 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir): "\n" "File Name Class Layout Document Label\n" "-------------------------------------------------------------\n" - "content/c000000000001.nwd NOVEL UNNUMBERED Chapter One\n" - "content/c000000000002.nwd NOVEL SCENE Scene One\n" - "content/b000000000002.nwd CHARACTER NOTE Jane Doe\n" + f"{pathA} NOVEL UNNUMBERED Chapter One\n" + f"{pathB} NOVEL SCENE Scene One\n" + f"{pathC} CHARACTER NOTE Jane Doe\n" ) # END Test testCoreTree_ToCFile diff --git a/tests/test_project.py b/tests/test_project.py index 75c72696..73c65f13 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -11,7 +11,6 @@ from zipfile import ZipFile from nwtools import cmpFiles from nw.core.project import NWProject -from nw.core.document import NWDoc from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple from nw.constants import nwConst, nwItemClass, nwItemType, nwItemLayout, nwFiles From fba4d5d04db3c1197adc17e36c9ccd16aad0c236 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Dec 2020 23:27:29 +0100 Subject: [PATCH 11/22] New test for OptionsState class --- nw/core/options.py | 18 +---- tests/README.md | 15 +++-- tests/test_core_options.py | 134 +++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 tests/test_core_options.py diff --git a/nw/core/options.py b/nw/core/options.py index 35358fc0..e0a937dc 100644 --- a/nw/core/options.py +++ b/nw/core/options.py @@ -176,11 +176,7 @@ class OptionState(): """ if getGroup in self.theState: if getName in self.theState[getGroup]: - try: - return self.theState[getGroup][getName] - except Exception as e: - logger.warning(str(e)) - return defaultValue + return self.theState[getGroup][getName] return defaultValue def getString(self, getGroup, getName, defaultValue): @@ -189,11 +185,7 @@ class OptionState(): """ if getGroup in self.theState: if getName in self.theState[getGroup]: - try: - return str(self.theState[getGroup][getName]) - except Exception as e: - logger.warning(str(e)) - return defaultValue + return str(self.theState[getGroup][getName]) return defaultValue def getInt(self, getGroup, getName, defaultValue): @@ -228,11 +220,7 @@ class OptionState(): """ if getGroup in self.theState: if getName in self.theState[getGroup]: - try: - return bool(self.theState[getGroup][getName]) - except Exception as e: - logger.warning(str(e)) - return defaultValue + return bool(self.theState[getGroup][getName]) return defaultValue ## diff --git a/tests/README.md b/tests/README.md index 7f9e737c..a559e00c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,11 +59,12 @@ Available markers are: To filter specific groups of tests, use the `-k` switch. The commands for the respective test categories are listed below. -| Type | Test Target | Source File(s) | Marker | Filter | -| :--- | :------------- | :------------------ | :-------- | :-------------------- | -| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | -| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | -| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | -| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | -| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | +| Type | Test Target | Source File(s) | Marker | Filter | +| :--- | :----------------- | :------------------ | :-------- | :-------------------- | +| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | +| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | +| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | +| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | +| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | +| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | diff --git a/tests/test_core_options.py b/tests/test_core_options.py new file mode 100644 index 00000000..20830d69 --- /dev/null +++ b/tests/test_core_options.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +"""novelWriter OptionState Class Tester +""" + +import os +import json +import pytest + +from nw.core import NWProject +from nw.core.options import OptionState +from nw.constants import nwFiles + +@pytest.mark.core +def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir): + """Test loading and saving from the OptionState class. + """ + theProject = NWProject(dummyGUI) + theOpts = OptionState(theProject) + + # Write a test file + optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE) + with open(optFile, mode="w+", encoding="utf8") as outFile: + json.dump({ + "GuiBuildNovel": { + "winWidth": 1000, + "winHeight": 700, + "addNovel": True, + "addNotes": False, + "textFont": "Cantarell", + "dummyItem": None, + }, + "DummyGroup": { + "dummyItem": None, + }, + }, outFile) + + # Load and save with no path set + theProject.projMeta = None + assert not theOpts.loadSettings() + assert not theOpts.saveSettings() + + # Set path + theProject.projMeta = tmpDir + assert theProject.projMeta == tmpDir + + # Cause open() to fail + def dummyIO(*args, **kwargs): + raise OSError + + monkeypatch.setattr("builtins.open", dummyIO) + assert not theOpts.loadSettings() + assert not theOpts.saveSettings() + monkeypatch.undo() + + # Load proper + assert theOpts.loadSettings() + + # Check that unwanted items have been removed + assert theOpts.theState == { + "GuiBuildNovel": { + "winWidth": 1000, + "winHeight": 700, + "addNovel": True, + "addNotes": False, + "textFont": "Cantarell", + }, + } + + # Save proper + assert theOpts.saveSettings() + + # Load again to check we get the values back + assert theOpts.loadSettings() + assert theOpts.theState == { + "GuiBuildNovel": { + "winWidth": 1000, + "winHeight": 700, + "addNovel": True, + "addNotes": False, + "textFont": "Cantarell", + }, + } + +# END Test testCoreOptions_LoadSave + +@pytest.mark.core +def testCoreOptions_SetGet(monkeypatch, dummyGUI, tmpDir): + """Test setting and getting values from the OptionState class. + """ + theProject = NWProject(dummyGUI) + theOpts = OptionState(theProject) + + # Set invalid values + assert not theOpts.setValue("DummyGroup", "dummyItem", None) + assert not theOpts.setValue("GuiBuildNovel", "dummyItem", None) + + # Set valid value + assert theOpts.setValue("GuiBuildNovel", "winWidth", 100) + + # Set some values of different types + assert theOpts.setValue("GuiBuildNovel", "winWidth", 100) + assert theOpts.setValue("GuiBuildNovel", "winHeight", 12.34) + assert theOpts.setValue("GuiBuildNovel", "addNovel", True) + assert theOpts.setValue("GuiBuildNovel", "textFont", "Cantarell") + + # Generic get, doesn't check type + assert theOpts.getValue("GuiBuildNovel", "winWidth", None) == 100 + assert theOpts.getValue("GuiBuildNovel", "winHeight", None) == 12.34 + assert theOpts.getValue("GuiBuildNovel", "addNovel", None) is True + assert theOpts.getValue("GuiBuildNovel", "textFont", None) == "Cantarell" + assert theOpts.getValue("GuiBuildNovel", "dummyItem", None) is None + + # Get type-specific + assert theOpts.getString("GuiBuildNovel", "winWidth", None) == "100" + assert theOpts.getString("GuiBuildNovel", "dummyItem", None) is None + assert theOpts.getInt("GuiBuildNovel", "winWidth", None) == 100 + assert theOpts.getInt("GuiBuildNovel", "textFont", None) is None + assert theOpts.getInt("GuiBuildNovel", "dummyItem", None) is None + assert theOpts.getFloat("GuiBuildNovel", "winWidth", None) == 100.0 + assert theOpts.getFloat("GuiBuildNovel", "textFont", None) is None + assert theOpts.getFloat("GuiBuildNovel", "dummyItem", None) is None + assert theOpts.getBool("GuiBuildNovel", "addNovel", None) is True + assert theOpts.getBool("GuiBuildNovel", "dummyItem", None) is None + + # Check integer validators + assert theOpts.validIntRange(5, 0, 9, 3) == 5 + assert theOpts.validIntRange(5, 0, 4, 3) == 3 + assert theOpts.validIntRange(5, 0, 5, 3) == 5 + assert theOpts.validIntRange(0, 0, 5, 3) == 0 + + assert theOpts.validIntTuple(0, (0, 1, 2), 3) == 0 + assert theOpts.validIntTuple(5, (0, 1, 2), 3) == 3 + +# END Test testCoreOptions_SetGet From c13bd18271b6838a079d05e566a1b9edb2a8a76d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Thu, 3 Dec 2020 17:39:42 +0100 Subject: [PATCH 12/22] Remove nw prefix from common test files --- tests/conftest.py | 2 +- tests/{nwdummy.py => dummy.py} | 12 ++++ tests/test_common.py | 2 +- tests/test_config.py | 2 +- tests/test_core_index.py | 2 +- tests/test_dialogs.py | 2 +- tests/test_gui.py | 2 +- tests/test_project.py | 123 +-------------------------------- tests/{nwtools.py => tools.py} | 0 9 files changed, 19 insertions(+), 128 deletions(-) rename tests/{nwdummy.py => dummy.py} (60%) rename tests/{nwtools.py => tools.py} (100%) diff --git a/tests/conftest.py b/tests/conftest.py index 3eeb8344..3a97c58f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest import shutil import os -from nwdummy import DummyMain +from dummy import DummyMain from PyQt5.QtWidgets import QMessageBox diff --git a/tests/nwdummy.py b/tests/dummy.py similarity index 60% rename from tests/nwdummy.py rename to tests/dummy.py index 4b5302b7..c97be669 100644 --- a/tests/nwdummy.py +++ b/tests/dummy.py @@ -2,6 +2,10 @@ """novelWriter Test Dummy GUI Classes """ +# =========================================================================== # +# Mock GUI +# =========================================================================== # + class DummyMain(): def __init__(self): @@ -37,3 +41,11 @@ class StatusBar(): return # END Class StatusBar + +# =========================================================================== # +# Error Functions +# Dummy functions that will raise errors instead. +# =========================================================================== # + +def dummyIO(*args, **kwargs): + raise OSError diff --git a/tests/test_common.py b/tests/test_common.py index 2a24f31f..135627fc 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -9,7 +9,7 @@ from nw.common import ( checkString, checkBool, checkInt, colRange, formatInt, transferCase, fuzzyTime, checkHandle, formatTimeStamp, formatTime ) -from nwtools import cmpList +from tools import cmpList @pytest.mark.core def testCheckString(): diff --git a/tests/test_config.py b/tests/test_config.py index c23f6bc6..94ff5d1c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,7 +5,7 @@ import pytest import os -from nwtools import cmpFiles +from tools import cmpFiles @pytest.mark.core def testConfigCore(tmpConf, tmpDir, refDir): diff --git a/tests/test_core_index.py b/tests/test_core_index.py index 1d577b70..ad87c7f3 100644 --- a/tests/test_core_index.py +++ b/tests/test_core_index.py @@ -8,7 +8,7 @@ import json from shutil import copyfile -from nwtools import cmpFiles +from tools import cmpFiles from nw.core.project import NWProject from nw.core.index import NWIndex diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index 7906c8e8..b8e3b276 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -9,7 +9,7 @@ import os import sys from shutil import copyfile -from nwtools import cmpFiles, getGuiItem +from tools import cmpFiles, getGuiItem from PyQt5.QtCore import Qt, QItemSelectionModel from PyQt5.QtWidgets import ( diff --git a/tests/test_gui.py b/tests/test_gui.py index 014d8f34..a6438d0a 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -9,7 +9,7 @@ import os import sys from shutil import copyfile -from nwtools import cmpFiles +from tools import cmpFiles from PyQt5.QtCore import Qt, QUrl, QPoint, QItemSelectionModel from PyQt5.QtGui import QTextCursor, QColor, QPixmap, QIcon, QTextBlock diff --git a/tests/test_project.py b/tests/test_project.py index 73c65f13..4167dbd3 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -8,7 +8,7 @@ import os from shutil import copyfile from zipfile import ZipFile -from nwtools import cmpFiles +from tools import cmpFiles from nw.core.project import NWProject from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple @@ -324,127 +324,6 @@ def testProjectMethods(monkeypatch, nwMinimal, dummyGUI): assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.bookAuthors == ["Jane Doe", "John Doh"] -@pytest.mark.project -def testSpellEnchant(tmpDir, nwConf): - wList = os.path.join(tmpDir, "wordlist.txt") - with open(wList, mode="w") as wFile: - wFile.write("a_word\nb_word\nc_word\n") - - spChk = NWSpellEnchant() - spChk.mainConf = nwConf - spChk.setLanguage("en", wList) - - assert spChk.checkWord("a_word") - assert spChk.checkWord("b_word") - assert spChk.checkWord("c_word") - assert not spChk.checkWord("d_word") - - spChk.addWord("d_word") - assert spChk.checkWord("d_word") - - wSuggest = spChk.suggestWords("wrod") - assert len(wSuggest) > 0 - assert "word" in wSuggest - - dList = spChk.listDictionaries() - assert len(dList) > 0 - - aTag, aName = spChk.describeDict() - assert aTag == "en" - assert aName != "" - -@pytest.mark.project -def testSpellSimple(tmpDir, nwConf): - wList = os.path.join(tmpDir, "wordlist.txt") - with open(wList, mode="w") as wFile: - wFile.write("a_word\nb_word\nc_word\n") - - spChk = NWSpellSimple() - spChk.mainConf = nwConf - spChk.setLanguage("en", wList) - - assert spChk.checkWord("a_word") - assert spChk.checkWord("b_word") - assert spChk.checkWord("c_word") - assert not spChk.checkWord("d_word") - - spChk.addWord("d_word") - assert spChk.checkWord("d_word") - - wSuggest = spChk.suggestWords("wrod") - assert len(wSuggest) > 0 - assert "word" in wSuggest - - dList = spChk.listDictionaries() - assert len(dList) > 0 - - aTag, aName = spChk.describeDict() - assert aTag == "en" - assert aName == nwConst.SP_INTERNAL - -@pytest.mark.project -def testProjectOptions(dummyGUI, nwLipsum): - """Test the class that holds all the GUI state user options that are - tied to the current open project. Non-project related GUI options - are handled by the Config class. - """ - theProject = NWProject(dummyGUI) - assert theProject.projMeta is None - - theOpts = theProject.optState - assert not theOpts.loadSettings() - assert not theOpts.saveSettings() - - # No Settings - assert theProject.openProject(nwLipsum) - assert theOpts.loadSettings() - assert theOpts.saveSettings() - assert str(theOpts.theState) == r"{}" - - # Read Invalid Settings and Filter - stateFile = os.path.join(theProject.projMeta, nwFiles.OPTS_FILE) - with open(stateFile, mode="w", encoding="utf8") as outFile: - outFile.write( - r'{"GuiProjectSettings": {"winWidth": 100, "winHeight": 50}, "NoGroup": {"NoName": 0}}' - ) - assert theOpts.loadSettings() - assert str(theOpts.theState) == r"{'GuiProjectSettings': {'winWidth': 100, 'winHeight': 50}}" - - # Set New Settings - assert not theOpts.setValue("NoGroup", "NoName", None) - assert not theOpts.setValue("GuiProjectSettings", "NoName", None) - assert theOpts.setValue("GuiProjectSettings", "winWidth", 200) - assert theOpts.setValue("GuiProjectSettings", "winHeight", 80) - assert str(theOpts.theState) == r"{'GuiProjectSettings': {'winWidth': 200, 'winHeight': 80}}" - - # Check Read/Write Types - - ## String - assert theOpts.setValue("GuiWritingStats", "winWidth", "123") - assert isinstance(theOpts.getString("GuiWritingStats", "winWidth", "456"), str) - assert theOpts.getString("GuiWritingStats", "NoName", "456") == "456" - - ## Int - assert theOpts.setValue("GuiWritingStats", "winWidth", "123") - assert isinstance(theOpts.getInt("GuiWritingStats", "winWidth", 456), int) - assert theOpts.getInt("GuiWritingStats", "NoName", 456) == 456 - assert theOpts.setValue("GuiWritingStats", "winWidth", "True") - assert theOpts.getInt("GuiWritingStats", "NoName", 456) == 456 - - ## Float - assert theOpts.setValue("GuiWritingStats", "winWidth", "123") - assert isinstance(theOpts.getFloat("GuiWritingStats", "winWidth", 456.0), float) - assert theOpts.getFloat("GuiWritingStats", "NoName", 456.0) == 456.0 - assert theOpts.setValue("GuiWritingStats", "winWidth", "True") - assert theOpts.getFloat("GuiWritingStats", "winWidth", 456.0) == 456.0 - - ## Bool - assert theOpts.setValue("GuiWritingStats", "winWidth", True) - assert isinstance(theOpts.getBool("GuiWritingStats", "winWidth", False), bool) - assert theOpts.getFloat("GuiWritingStats", "NoName", False) is False - assert theOpts.setValue("GuiWritingStats", "winWidth", "True") - assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False - @pytest.mark.project def testProjectOrphanedFiles(dummyGUI, nwLipsum): """Check that files in the content folder that are not tracked in diff --git a/tests/nwtools.py b/tests/tools.py similarity index 100% rename from tests/nwtools.py rename to tests/tools.py From c7c70a35a08d17b690823fa038bb7415e0c786d7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Thu, 3 Dec 2020 18:37:52 +0100 Subject: [PATCH 13/22] Updated tests for NWSpell* classes --- nw/core/spellcheck.py | 32 ++++--- tests/README.md | 18 ++-- tests/dummy.py | 2 +- tests/test_core_document.py | 9 +- tests/test_core_options.py | 7 +- tests/test_core_spell.py | 172 ++++++++++++++++++++++++++++++++++++ tests/tools.py | 12 +++ 7 files changed, 219 insertions(+), 33 deletions(-) create mode 100644 tests/test_core_spell.py diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py index b2f92a96..83ff2540 100644 --- a/nw/core/spellcheck.py +++ b/nw/core/spellcheck.py @@ -28,8 +28,7 @@ import nw import logging import os - -from difflib import get_close_matches +import difflib from nw.constants import nwConst, isoLanguage @@ -70,14 +69,16 @@ class NWSpellCheck(): """ if self.projectDict is not None and newWord not in self.projDict: newWord = newWord.strip() - self.projDict.append(newWord) try: with open(self.projectDict, mode="a+", encoding="utf-8") as outFile: outFile.write("%s\n" % newWord) + self.projDict.append(newWord) except Exception as e: logger.error("Failed to add word to project word list %s" % str(self.projectDict)) logger.error(str(e)) - return + return False + return True + return False def listDictionaries(self): """Dummy function. @@ -109,9 +110,12 @@ class NWSpellCheck(): """ self.projDict = [] if projectDict is not None: - self.projectDict = projectDict if not os.path.isfile(projectDict): - return + self.projectDict = None + return False + else: + self.projectDict = projectDict + try: logger.debug("Loading project word list") with open(projectDict, mode="r", encoding="utf-8") as wordsFile: @@ -123,7 +127,9 @@ class NWSpellCheck(): except Exception as e: logger.error("Failed to load project word list") logger.error(str(e)) - return + return False + + return True # END Class NWSpellCheck @@ -287,7 +293,7 @@ class NWSpellSimple(NWSpellCheck): if len(theWord) == 0: return [] - theMatches = get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75) + theMatches = difflib.get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75) theOptions = [] for aWord in theMatches: if len(aWord) == 0: @@ -314,14 +320,12 @@ class NWSpellSimple(NWSpellCheck): retList = [] for dictFile in os.listdir(self.mainConf.dictPath): - theBits = os.path.splitext(dictFile) - if len(theBits) != 2: - continue - if theBits[1] != ".dict": + fRoot, fExt = os.path.splitext(dictFile) + if fExt != ".dict": continue - spName = "%s [%s]" % (self.expandLanguage(theBits[0]), nwConst.SP_INTERNAL) - retList.append((theBits[0], spName)) + spName = "%s [%s]" % (self.expandLanguage(fRoot), nwConst.SP_INTERNAL) + retList.append((fRoot, spName)) return retList diff --git a/tests/README.md b/tests/README.md index a559e00c..51dd9dca 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,12 +59,12 @@ Available markers are: To filter specific groups of tests, use the `-k` switch. The commands for the respective test categories are listed below. -| Type | Test Target | Source File(s) | Marker | Filter | -| :--- | :----------------- | :------------------ | :-------- | :-------------------- | -| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | -| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | -| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | -| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | -| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | -| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | - +| Type | Test Target | Source File(s) | Marker | Filter | +| :--- | :----------------- | :-------------------- | :-------- | :-------------------- | +| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | +| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | +| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | +| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | +| Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` | +| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | +| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | diff --git a/tests/dummy.py b/tests/dummy.py index c97be669..fbb91cbd 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -47,5 +47,5 @@ class StatusBar(): # Dummy functions that will raise errors instead. # =========================================================================== # -def dummyIO(*args, **kwargs): +def causeOSError(*args, **kwargs): raise OSError diff --git a/tests/test_core_document.py b/tests/test_core_document.py index 904c9d25..9e16c66e 100644 --- a/tests/test_core_document.py +++ b/tests/test_core_document.py @@ -5,6 +5,8 @@ import os import pytest +from dummy import causeOSError + from nw.core import NWProject, NWDoc from nw.core.item import NWItem from nw.constants import nwItemClass, nwItemLayout @@ -75,10 +77,7 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal): assert inFile.read() == theText # Cause open() to fail while saving - def dummyIO(*args, **kwargs): - raise OSError - - monkeypatch.setattr("builtins.open", dummyIO) + monkeypatch.setattr("builtins.open", causeOSError) assert not theDoc.saveDocument(theText) monkeypatch.undo() @@ -91,7 +90,7 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal): assert os.path.isfile(docPath) # Cause the delete to fail - monkeypatch.setattr("os.unlink", dummyIO) + monkeypatch.setattr("os.unlink", causeOSError) assert not theDoc.deleteDocument(xHandle) monkeypatch.undo() diff --git a/tests/test_core_options.py b/tests/test_core_options.py index 20830d69..963a12c4 100644 --- a/tests/test_core_options.py +++ b/tests/test_core_options.py @@ -6,6 +6,8 @@ import os import json import pytest +from dummy import causeOSError + from nw.core import NWProject from nw.core.options import OptionState from nw.constants import nwFiles @@ -44,10 +46,7 @@ def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir): assert theProject.projMeta == tmpDir # Cause open() to fail - def dummyIO(*args, **kwargs): - raise OSError - - monkeypatch.setattr("builtins.open", dummyIO) + monkeypatch.setattr("builtins.open", causeOSError) assert not theOpts.loadSettings() assert not theOpts.saveSettings() monkeypatch.undo() diff --git a/tests/test_core_spell.py b/tests/test_core_spell.py new file mode 100644 index 00000000..6b6d7207 --- /dev/null +++ b/tests/test_core_spell.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +"""novelWriter Spell Check Class Tester +""" + +import os +import sys +import pytest + +from difflib import get_close_matches + +from dummy import causeOSError +from tools import readFile, writeFile + +from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple +from nw.constants import nwConst + +@pytest.mark.core +def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf): + """Test the spell checker super class + """ + wList = os.path.join(tmpDir, "wordlist.txt") + writeFile(wList, "a_word\nb_word\nc_word\n") + + spChk = NWSpellCheck() + spChk.mainConf = tmpConf + + # Check that dummy functions return results that reflects that spell + # checking is effectively disabled + assert spChk.setLanguage("", "") is None + assert spChk.checkWord("") + assert spChk.suggestWords("") == [] + assert spChk.listDictionaries() == [] + assert spChk.describeDict() == ("", "") + + # Check language info + assert NWSpellCheck.expandLanguage("en") == "English" + assert NWSpellCheck.expandLanguage("en_GB") == "English (GB)" + + # Add a word to the user's dictionary + assert spChk._readProjectDictionary("dummy") is False + monkeypatch.setattr("builtins.open", causeOSError) + assert spChk._readProjectDictionary(wList) is False + monkeypatch.undo() + assert spChk._readProjectDictionary(wList) is True + assert spChk.projectDict == wList + + # Cannot write to file + monkeypatch.setattr("builtins.open", causeOSError) + assert spChk.addWord("d_word") is False + monkeypatch.undo() + assert readFile(wList) == "a_word\nb_word\nc_word\n" + + # First time, OK + assert spChk.addWord("d_word") is True + assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n" + + # But not added twice + assert spChk.addWord("d_word") is False + assert readFile(wList) == "a_word\nb_word\nc_word\nd_word\n" + +# END Test testCoreSpell_Super + +@pytest.mark.core +def testCoreSpell_Enchant(monkeypatch, tmpDir, tmpConf): + """Test the pyenchant spell checker + """ + wList = os.path.join(tmpDir, "wordlist.txt") + writeFile(wList, "a_word\nb_word\nc_word\n") + + # Block the enchant package (and trigger the dummy class) + monkeypatch.setitem(sys.modules, "enchant", None) + spChk = NWSpellEnchant() + + spChk.setLanguage("en", wList) + assert spChk.setLanguage("", "") is None + assert spChk.checkWord("") + assert spChk.suggestWords("") == [] + assert spChk.listDictionaries() == [] + assert spChk.describeDict() == ("", "") + + monkeypatch.undo() + + # Load the proper enchant package + spChk = NWSpellEnchant() + spChk.mainConf = tmpConf + spChk.setLanguage("en", wList) + + assert spChk.checkWord("a_word") + assert spChk.checkWord("b_word") + assert spChk.checkWord("c_word") + assert not spChk.checkWord("d_word") + + spChk.addWord("d_word") + assert spChk.checkWord("d_word") + + wSuggest = spChk.suggestWords("wrod") + assert len(wSuggest) > 0 + assert "word" in wSuggest + + dList = spChk.listDictionaries() + assert len(dList) > 0 + + aTag, aName = spChk.describeDict() + assert aTag == "en" + assert aName != "" + +# END Test testCoreSpell_Enchant + +@pytest.mark.core +def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf): + """Test the fallback simple spell checker + """ + wList = os.path.join(tmpDir, "wordlist.txt") + wDict = os.path.join(tmpDir, "en.dict") + writeFile(wList, "a_word\nb_word\nc_word\n") + writeFile(wDict, "# Comment\ne_word\nf_word\ng_word\n") + + spChk = NWSpellSimple() + spChk.mainConf = tmpConf + spChk.mainConf.dictPath = tmpDir + + # Load dictionary, but fail + monkeypatch.setattr("builtins.open", causeOSError) + spChk.setLanguage("en", wList) + assert spChk.spellLanguage is None + assert spChk.WORDS == spChk.projDict + monkeypatch.undo() + + # Load dictionary properly + spChk.setLanguage("en", wList) + assert spChk.projDict == ["a_word", "b_word", "c_word"] + assert spChk.WORDS == ["e_word", "f_word", "g_word", "a_word", "b_word", "c_word"] + + # Check words + assert spChk.checkWord("a_word") + assert spChk.checkWord("b_word") + assert spChk.checkWord("c_word") + assert not spChk.checkWord("d_word") + assert spChk.checkWord("e_word") + assert spChk.checkWord("f_word") + assert spChk.checkWord("g_word") + + # Add word + spChk.addWord("d_word") + assert spChk.checkWord("d_word") + + # Check spelling + assert spChk.suggestWords(" \t") == [] + + wSuggest = spChk.suggestWords("d_wrod") + assert len(wSuggest) > 0 + assert "d_word" in wSuggest + + # Break the matching + monkeypatch.setattr("difflib.get_close_matches", lambda *args, **kwargs: [""]) + assert spChk.suggestWords("word") == [] + monkeypatch.undo() + + # Capitalisation + wSuggest = spChk.suggestWords("D_wrod") + assert len(wSuggest) > 0 + assert "D_word" in wSuggest + + # List dictionaries + assert spChk.listDictionaries() == [("en", "English [%s]" % nwConst.SP_INTERNAL)] + + # Description + aTag, aName = spChk.describeDict() + assert aTag == "en" + assert aName == nwConst.SP_INTERNAL + +# END Test testCoreSpell_Simple diff --git a/tests/tools.py b/tests/tools.py index e35d9e08..185a9fa0 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -70,3 +70,15 @@ def getGuiItem(theName): if qWidget.objectName() == theName: return qWidget return None + +def readFile(fileName): + """Returns the content of a file as a string. + """ + with open(fileName, mode="r", encoding="utf8") as inFile: + return inFile.read() + +def writeFile(fileName, fileData): + """Write the contents of a string to a file. + """ + with open(fileName, mode="w", encoding="utf8") as outFile: + outFile.write(fileData) From 0cb36c02f3eb0cf32af451fa05ece5c025d29055 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Thu, 3 Dec 2020 20:22:22 +0100 Subject: [PATCH 14/22] New test for NWStatus class --- nw/core/status.py | 16 ++--- tests/README.md | 1 + tests/test_core_spell.py | 2 - tests/test_core_status.py | 126 ++++++++++++++++++++++++++++++++++++++ tests/test_project.py | 3 +- 5 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 tests/test_core_status.py diff --git a/nw/core/status.py b/nw/core/status.py index 7a1c7127..2c6fe811 100644 --- a/nw/core/status.py +++ b/nw/core/status.py @@ -79,6 +79,7 @@ class NWStatus(): theStatus = checkInt(theStatus, 0, False) if theStatus >= 0 and theStatus < self._theLength: return self._theLabels[theStatus] + return self._theLabels[0] def setNewEntries(self, newList): """Update the list of entries after they have been modified by @@ -136,18 +137,9 @@ class NWStatus(): for xChild in xParent: theLabels.append(xChild.text) - if "red" in xChild.attrib: - cR = checkInt(xChild.attrib["red"], 0, False) - else: - cR = 0 - if "green" in xChild.attrib: - cG = checkInt(xChild.attrib["green"], 0, False) - else: - cG = 0 - if "blue" in xChild.attrib: - cB = checkInt(xChild.attrib["blue"], 0, False) - else: - cB = 0 + cR = checkInt(xChild.attrib.get("red", 0), 0, False) + cG = checkInt(xChild.attrib.get("green", 0), 0, False) + cB = checkInt(xChild.attrib.get("blue", 0), 0, False) theColours.append((cR, cG, cB)) if len(theLabels) > 0: diff --git a/tests/README.md b/tests/README.md index 51dd9dca..797fa2ea 100644 --- a/tests/README.md +++ b/tests/README.md @@ -66,5 +66,6 @@ The commands for the respective test categories are listed below. | Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | | Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | | Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` | +| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` | | Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | | Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | diff --git a/tests/test_core_spell.py b/tests/test_core_spell.py index 6b6d7207..1ef94816 100644 --- a/tests/test_core_spell.py +++ b/tests/test_core_spell.py @@ -6,8 +6,6 @@ import os import sys import pytest -from difflib import get_close_matches - from dummy import causeOSError from tools import readFile, writeFile diff --git a/tests/test_core_status.py b/tests/test_core_status.py new file mode 100644 index 00000000..515b4b47 --- /dev/null +++ b/tests/test_core_status.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +"""novelWriter Status Class Tester +""" + +import pytest + +from lxml import etree + +from nw.core.status import NWStatus + +@pytest.mark.core +def testCoreStatus_Entries(): + """Test all the simple setters for the NWItem class. + """ + theStatus = NWStatus() + + # Add entries + theStatus.addEntry("New", (100, 100, 100)) + theStatus.addEntry("Minor", (200, 50, 0)) + theStatus.addEntry("Major", (200, 150, 0)) + theStatus.addEntry("Main", (50, 200, 0)) + + assert theStatus._theLabels == ["New", "Minor", "Major", "Main"] + assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)] + assert theStatus._theCounts == [0, 0, 0, 0] + assert theStatus._theMap["New"] == 0 + assert theStatus._theMap["Minor"] == 1 + assert theStatus._theMap["Major"] == 2 + assert theStatus._theMap["Main"] == 3 + assert theStatus._theLength == 4 + + # Lookups + assert theStatus.lookupEntry(None) is None + assert theStatus.lookupEntry("dummy") is None + assert theStatus.lookupEntry("Main") == 3 + + # Checks + assert theStatus.checkEntry(123) == "New" + assert theStatus.checkEntry("Stuff") == "New" + assert theStatus.checkEntry("New ") == "New" + assert theStatus.checkEntry(" Main ") == "Main" + + # Set new list + newList = [ + ("New", 1, 1, 1, "New"), + ("Minor", 2, 2, 2, "Minor"), + ("Major", 3, 3, 3, "Major"), + ("Min", 4, 4, 4, "Main"), + ("Max", 5, 5, 5, None), + ] + assert theStatus.setNewEntries(None) == {} + assert theStatus.setNewEntries(newList) == {"Main": "Min"} + + assert theStatus._theLabels == ["New", "Minor", "Major", "Min", "Max"] + assert theStatus._theColours == [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)] + assert theStatus._theCounts == [0, 0, 0, 0, 0] + assert theStatus._theMap["New"] == 0 + assert theStatus._theMap["Minor"] == 1 + assert theStatus._theMap["Major"] == 2 + assert theStatus._theMap["Min"] == 3 + assert theStatus._theMap["Max"] == 4 + assert theStatus._theLength == 5 + + # Add counts + countTo = [3, 5, 7, 9, 11] + for i, n in enumerate(countTo): + for _ in range(n): + theStatus.countEntry(theStatus._theLabels[i]) + assert theStatus._theCounts == countTo + + # Iterate + for i, (sA, sB, sC) in enumerate(theStatus): + assert sA == theStatus._theLabels[i] + assert sB == theStatus._theColours[i] + assert sC == theStatus._theCounts[i] + + assert theStatus[9] == (None, None, None) + + # Clear counts + theStatus.resetCounts() + assert theStatus._theCounts == [0, 0, 0, 0, 0] + +# END Test testCoreStatus_Entries + +@pytest.mark.core +def testCoreStatus_XMLPackUnpack(): + """Test all the simple setters for the NWItem class. + """ + theStatus = NWStatus() + theStatus.addEntry("New", (100, 100, 100)) + theStatus.addEntry("Minor", (200, 50, 0)) + theStatus.addEntry("Major", (200, 150, 0)) + theStatus.addEntry("Main", (50, 200, 0)) + + countTo = [3, 5, 7, 9] + for i, n in enumerate(countTo): + for _ in range(n): + theStatus.countEntry(theStatus._theLabels[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"Minor" + b"Major" + b"Main" + b"" + ) + + # Unpack + theStatus = NWStatus() + assert theStatus.unpackXML(xStatus) + assert theStatus._theLabels == ["New", "Minor", "Major", "Main"] + assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)] + assert theStatus._theCounts == [0, 0, 0, 0] + assert theStatus._theMap["New"] == 0 + assert theStatus._theMap["Minor"] == 1 + assert theStatus._theMap["Major"] == 2 + assert theStatus._theMap["Main"] == 3 + assert theStatus._theLength == 4 + +# END Test testCoreStatus_XMLPackUnpack diff --git a/tests/test_project.py b/tests/test_project.py index 4167dbd3..748021a7 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -11,8 +11,7 @@ from zipfile import ZipFile from tools import cmpFiles from nw.core.project import NWProject -from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple -from nw.constants import nwConst, nwItemClass, nwItemType, nwItemLayout, nwFiles +from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles @pytest.mark.project def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI): From b687ffe6968595cdc997f99e2eb00571cb33266c Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 4 Dec 2020 00:46:29 +0100 Subject: [PATCH 15/22] New test for Tokenizer class --- nw/core/tokenizer.py | 15 +- tests/README.md | 3 +- tests/lipsum/ToC.json | 77 ----- tests/minimal/ToC.json | 17 - tests/test_core_tokenizer.py | 645 +++++++++++++++++++++++++++++++++++ 5 files changed, 655 insertions(+), 102 deletions(-) delete mode 100644 tests/lipsum/ToC.json delete mode 100644 tests/minimal/ToC.json create mode 100644 tests/test_core_tokenizer.py diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index 7870493c..6039121d 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -180,7 +180,7 @@ class Tokenizer(): ## def addRootHeading(self, theHandle): - """Add a heading at the start if a new root folder. + """Add a heading at the start of a new root folder. """ theItem = self.theProject.projTree[theHandle] if theItem is None: @@ -205,7 +205,7 @@ class Tokenizer(): self.theHandle = theHandle self.theItem = self.theProject.projTree[theHandle] if self.theItem is None: - return + return False if theText is not None: # If the text is set, just use that @@ -234,7 +234,7 @@ class Tokenizer(): self.isNote = self.theItem.itemLayout == nwItemLayout.NOTE self.isNovel = self.isBook or self.isUnNum or self.isChap or self.isScene - return + return True def getResult(self): """Return the result from the conversion. @@ -244,6 +244,8 @@ class Tokenizer(): def getResultSize(self): """Return the size of the result from the conversion. """ + if self.theResult is None: + return 0 return len(self.theResult) def getFilteredMarkdown(self): @@ -445,7 +447,7 @@ class Tokenizer(): """ # No special header formatting for notes and no-layout files if self.isNone or self.isNote: - return + return False # For novel files, we need to handle chapter numbering, scene # numbering, and scene breaks @@ -479,8 +481,7 @@ class Tokenizer(): if self.isUnNum: tTemp = self._formatHeading(self.fmtUnNum, tToken[2]) elif tToken[2].startswith("*"): - tTemp = self._formatHeading(self.fmtUnNum, tToken[2]) - tTemp = tTemp[1:].lstrip() + tTemp = self._formatHeading(self.fmtUnNum, tToken[2][1:].lstrip()) else: self.numChapter += 1 tTemp = self._formatHeading(self.fmtChapter, tToken[2]) @@ -663,7 +664,7 @@ class Tokenizer(): self.A_LEFT ) - return + return True ## # Internal Functions diff --git a/tests/README.md b/tests/README.md index 797fa2ea..b9c221f9 100644 --- a/tests/README.md +++ b/tests/README.md @@ -52,7 +52,7 @@ pytest-3 -v -m core Available markers are: -* '`core`' for test covering the classes in the `nw/core` folder +* '`core`' for unit tests covering the classes in the `nw/core` folder ## Tests @@ -69,3 +69,4 @@ The commands for the respective test categories are listed below. | Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` | | Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | | Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | +| Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` | diff --git a/tests/lipsum/ToC.json b/tests/lipsum/ToC.json deleted file mode 100644 index 4d22a21c..00000000 --- a/tests/lipsum/ToC.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - [ - "content/04468803b92e1.nwd", - "WORLD", - "Ancient Europe" - ], - [ - "content/2426c6f0ca922.nwd", - "PLOT", - "Main" - ], - [ - "content/441420a886d82.nwd", - "NOVEL", - "Chapter Two" - ], - [ - "content/47666c91c7ccf.nwd", - "NOVEL", - "Scene Five" - ], - [ - "content/4c4f28287af27.nwd", - "CHARACTER", - "Mr. Nobody" - ], - [ - "content/7a992350f3eb6.nwd", - "NOVEL", - "Lorem Ipsum" - ], - [ - "content/846352075de7d.nwd", - "NOVEL", - "Interlude" - ], - [ - "content/88243afbe5ed8.nwd", - "NOVEL", - "Scene One" - ], - [ - "content/88d59a277361b.nwd", - "NOVEL", - "Prologue" - ], - [ - "content/8c58a65414c23.nwd", - "NOVEL", - "Front Matter" - ], - [ - "content/db7e733775d4d.nwd", - "NOVEL", - "Act One" - ], - [ - "content/eb103bc70c90c.nwd", - "NOVEL", - "Scene Three" - ], - [ - "content/f8c0562e50f1b.nwd", - "NOVEL", - "Scene Four" - ], - [ - "content/f96ec11c6a3da.nwd", - "NOVEL", - "Scene Two" - ], - [ - "content/fb609cd8319dc.nwd", - "NOVEL", - "Chapter One" - ] -] \ No newline at end of file diff --git a/tests/minimal/ToC.json b/tests/minimal/ToC.json deleted file mode 100644 index 5881c392..00000000 --- a/tests/minimal/ToC.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - [ - "content/8c659a11cd429.nwd", - "NOVEL", - "New Scene" - ], - [ - "content/a35baf2e93843.nwd", - "NOVEL", - "Title Page" - ], - [ - "content/f5ab3e30151e1.nwd", - "NOVEL", - "New Chapter" - ] -] \ No newline at end of file diff --git a/tests/test_core_tokenizer.py b/tests/test_core_tokenizer.py new file mode 100644 index 00000000..559f3a1e --- /dev/null +++ b/tests/test_core_tokenizer.py @@ -0,0 +1,645 @@ +# -*- coding: utf-8 -*- +"""novelWriter Tokenizer Class Tester +""" + +import pytest + +from nw.core import NWProject, NWDoc +from nw.core.tokenizer import Tokenizer + +@pytest.mark.core +def testCoreToken_Setters(dummyGUI): + """Test all the setters for the Tokenizer class. + """ + theProject = NWProject(dummyGUI) + theToken = Tokenizer(dummyGUI, theProject) + + # Verify defaults + assert theToken.fmtTitle == "%title%" + assert theToken.fmtChapter == "%title%" + assert theToken.fmtUnNum == "%title%" + assert theToken.fmtScene == "%title%" + assert theToken.fmtSection == "%title%" + assert theToken.hideScene is False + assert theToken.hideSection is False + assert theToken.linkHeaders is False + assert theToken.doBodyText is True + assert theToken.doSynopsis is False + assert theToken.doComments is False + assert theToken.doKeywords is False + assert theToken.doJustify is False + + # Set new values + theToken.setTitleFormat("T: %title%") + theToken.setChapterFormat("C: %title%") + theToken.setUnNumberedFormat("U: %title%") + theToken.setSceneFormat("S: %title%", True) + theToken.setSectionFormat("X: %title%", True) + theToken.setLinkHeaders(True) + theToken.setBodyText(False) + theToken.setSynopsis(True) + theToken.setComments(True) + theToken.setKeywords(True) + theToken.setJustify(True) + + # Check new values + assert theToken.fmtTitle == "T: %title%" + assert theToken.fmtChapter == "C: %title%" + assert theToken.fmtUnNum == "U: %title%" + assert theToken.fmtScene == "S: %title%" + assert theToken.fmtSection == "X: %title%" + assert theToken.hideScene is True + assert theToken.hideSection is True + assert theToken.linkHeaders is True + assert theToken.doBodyText is False + assert theToken.doSynopsis is True + assert theToken.doComments is True + assert theToken.doKeywords is True + assert theToken.doJustify is True + +# END Test testCoreToken_Setters + +@pytest.mark.core +def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI): + """Test handling files and text in the Tokenizer class. + """ + theProject = NWProject(dummyGUI) + theProject.projTree.setSeed(42) + theToken = Tokenizer(theProject, dummyGUI) + + assert theProject.openProject(nwMinimal) + sHandle = "8c659a11cd429" + + # Set some content to work with + + docText = ( + "### Scene Six\n\n" + "This is text with _italic text_, some **bold text**, some ~~deleted text~~, " + "and some **_mixed text_** and **some _nested_ text**.\n\n" + "#### Replace\n\n" + "Also, replace and .\n\n" + ) + docTextR = docText.replace("", "this").replace("", "that") + + nDoc = NWDoc(theProject, dummyGUI) + nDoc.openDocument(sHandle) + nDoc.saveDocument(docText) + nDoc.clearDocument() + + theProject.setAutoReplace({"A": "this", "B": "that"}) + + assert theProject.saveProject() + + # Root heading + assert theToken.addRootHeading("dummy") is False + assert theToken.addRootHeading(sHandle) is False + assert theToken.addRootHeading("7695ce551d265") is True + assert theToken.theMarkdown == "# Notes: Plot\n\n" + + # Set text + assert theToken.setText("dummy") is False + assert theToken.setText(sHandle) is True + assert theToken.theText == docText + + monkeypatch.setattr("nw.constants.nwConst.MAX_DOCSIZE", 100) + assert theToken.setText(sHandle, docText) is True + assert theToken.theText == ( + "# ERROR\n\n" + "Document 'New Scene' is too big (0.00 MB). Skipping.\n\n" + ) + monkeypatch.undo() + + assert theToken.setText(sHandle, docText) is True + assert theToken.theText == docText + + assert theToken.isNone is False + assert theToken.isTitle is False + assert theToken.isBook is False + assert theToken.isPage is False + assert theToken.isPart is False + assert theToken.isUnNum is False + assert theToken.isChap is False + assert theToken.isScene is True + assert theToken.isNote is False + assert theToken.isNovel is True + + # Auto replace + theToken.doAutoReplace() + assert theToken.theText == docTextR + + # Access + assert theToken.getResult() is None + assert theToken.getResultSize() == 0 + theToken.theResult = "" + assert theToken.getResultSize() == 0 + + # Post Processing + theToken.theResult = r"This is text with escapes: \** \~~ \__" + theToken.doPostProcessing() + assert theToken.theResult == "This is text with escapes: ** ~~ __" + +# END Test testCoreToken_TextOps + +@pytest.mark.core +def testCoreToken_Tokenize(dummyGUI): + """Test the tokenization of the Tokenizer class. + """ + theProject = NWProject(dummyGUI) + theToken = Tokenizer(theProject, dummyGUI) + + # Header 1 + theToken.theText = "# Novel Title\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD1, 1, "Novel Title", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "# Novel Title\n\n" + + # Header 2 + theToken.theText = "## Chapter One\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "## Chapter One\n\n" + + # Header 3 + theToken.theText = "### Scene One\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD3, 1, "Scene One", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "### Scene One\n\n" + + # Header 4 + theToken.theText = "#### A Section\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD4, 1, "A Section", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "#### A Section\n\n" + + # Comment + theToken.theText = "% A comment\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_COMMENT, 1, "A comment", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "\n" + + theToken.setComments(True) + theToken.tokenizeText() + assert theToken.theMarkdown == "% A comment\n\n" + + # Symopsis + theToken.theText = "%synopsis: The synopsis\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + theToken.theText = "% synopsis: The synopsis\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "\n" + + theToken.setSynopsis(True) + theToken.tokenizeText() + assert theToken.theMarkdown == "% synopsis: The synopsis\n\n" + + # Keyword + theToken.theText = "@char: Bod\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_KEYWORD, 1, "char: Bod", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "\n" + + theToken.setKeywords(True) + theToken.tokenizeText() + assert theToken.theMarkdown == "@char: Bod\n\n" + + # Text + theToken.theText = "Some plain text\non two lines\n\n\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_TEXT, 1, "Some plain text", [], Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 2, "on two lines", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "Some plain text\non two lines\n\n\n\n" + + theToken.setBodyText(False) + theToken.tokenizeText() + assert theToken.theTokens == [ + (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "\n\n\n" + theToken.setBodyText(True) + + # Text Emphasis + theToken.theText = "Some **bolded text** on this lines\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + ( + Tokenizer.T_TEXT, 1, + "Some **bolded text** on this lines", + [ + [5, 2, Tokenizer.FMT_B_B], + [18, 2, Tokenizer.FMT_B_E], + ], + Tokenizer.A_NONE + ), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "Some **bolded text** on this lines\n\n" + + theToken.theText = "Some _italic text_ on this lines\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + ( + Tokenizer.T_TEXT, 1, + "Some _italic text_ on this lines", + [ + [5, 1, Tokenizer.FMT_I_B], + [17, 1, Tokenizer.FMT_I_E], + ], + Tokenizer.A_NONE + ), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "Some _italic text_ on this lines\n\n" + + theToken.theText = "Some **_bold italic text_** on this lines\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + ( + Tokenizer.T_TEXT, 1, + "Some **_bold italic text_** on this lines", + [ + [5, 2, Tokenizer.FMT_B_B], + [7, 1, Tokenizer.FMT_I_B], + [24, 1, Tokenizer.FMT_I_E], + [25, 2, Tokenizer.FMT_B_E], + ], + Tokenizer.A_NONE + ), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "Some **_bold italic text_** on this lines\n\n" + + theToken.theText = "Some ~~strikethrough text~~ on this lines\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + ( + Tokenizer.T_TEXT, 1, + "Some ~~strikethrough text~~ on this lines", + [ + [5, 2, Tokenizer.FMT_D_B], + [25, 2, Tokenizer.FMT_D_E], + ], + Tokenizer.A_NONE + ), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == "Some ~~strikethrough text~~ on this lines\n\n" + + theToken.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + theToken.tokenizeText() + assert theToken.theTokens == [ + ( + Tokenizer.T_TEXT, 1, + "Some **nested bold and _italic_ and ~~strikethrough~~ text** here", + [ + [5, 2, Tokenizer.FMT_B_B], + [23, 1, Tokenizer.FMT_I_B], + [30, 1, Tokenizer.FMT_I_E], + [36, 2, Tokenizer.FMT_D_B], + [51, 2, Tokenizer.FMT_D_E], + [58, 2, Tokenizer.FMT_B_E], + ], + Tokenizer.A_NONE + ), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + assert theToken.theMarkdown == ( + "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" + ) + + # Check the markdown function as well + assert theToken.getFilteredMarkdown() == ( + "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" + ) + +# END Test testCoreToken_Tokenize + +@pytest.mark.core +def testCoreToken_Headers(dummyGUI): + """Test the header and page parser of the Tokenizer class. + """ + theProject = NWProject(dummyGUI) + theToken = Tokenizer(theProject, dummyGUI) + + # Nothing + theToken.theText = "Some text ...\n" + assert theToken.doHeaders() is True + theToken.isNone = True + assert theToken.doHeaders() is False + theToken.isNone = False + assert theToken.doHeaders() is True + theToken.isNote = True + assert theToken.doHeaders() is False + theToken.isNote = False + + ## + # Novel + ## + + theToken.isNovel = True + + # Titles + # ====== + + # H1: Title + theToken.theText = "# Novel Title\n" + theToken.setTitleFormat(r"T: %title%") + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD1, 1, "T: Novel Title", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # Chapters + # ======== + + # H2: Chapter + theToken.theText = "## Chapter One\n" + theToken.setChapterFormat(r"C: %title%") + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "C: Chapter One", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H2: Unnumbered Chapter + theToken.theText = "## Chapter One\n" + theToken.setUnNumberedFormat(r"U: %title%") + theToken.isUnNum = True + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "U: Chapter One", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H2: Unnumbered Chapter with Star + theToken.theText = "## *Prologue\n" + theToken.setUnNumberedFormat(r"U: %title%") + theToken.isUnNum = False + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "U: Prologue", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H2: Chapter Word Number + theToken.theText = "## Chapter\n" + theToken.setChapterFormat(r"Chapter %chw%") + theToken.numChapter = 0 + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H2: Chapter Roman Number Upper Case + theToken.theText = "## Chapter\n" + theToken.setChapterFormat(r"Chapter %chI%") + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "Chapter II", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H2: Chapter Roman Number Lower Case + theToken.theText = "## Chapter\n" + theToken.setChapterFormat(r"Chapter %chi%") + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD2, 1, "Chapter iii", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # Scenes + # ====== + + # H3: Scene w/Title + theToken.theText = "### Scene One\n" + theToken.setSceneFormat(r"S: %title%", False) + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD3, 1, "S: Scene One", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene Hidden wo/Format + theToken.theText = "### Scene One\n" + theToken.setSceneFormat(r"", True) + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene wo/Format, first + theToken.theText = "### Scene One\n" + theToken.setSceneFormat(r"", False) + theToken.firstScene = True + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene wo/Format, not first + theToken.theText = "### Scene One\n" + theToken.setSceneFormat(r"", False) + theToken.firstScene = False + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene Separator, first + theToken.theText = "### Scene One\n" + theToken.setSceneFormat(r"* * *", False) + theToken.firstScene = True + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene Separator, not first + theToken.theText = "### Scene One\n" + theToken.setSceneFormat(r"* * *", False) + theToken.firstScene = False + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene w/Absolute Number + theToken.theText = "### A Scene\n" + theToken.setSceneFormat(r"Scene %sca%", False) + theToken.numAbsScene = 0 + theToken.numChScene = 0 + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD3, 1, "Scene 1", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H3: Scene w/Chapter Number + theToken.theText = "### A Scene\n" + theToken.setSceneFormat(r"Scene %ch%.%sc%", False) + theToken.numAbsScene = 0 + theToken.numChScene = 1 + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD3, 1, "Scene 3.2", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # Sections + # ======== + + # H4: Section Hidden wo/Format + theToken.theText = "#### A Section\n" + theToken.setSectionFormat(r"", True) + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H4: Section Visible wo/Format + theToken.theText = "#### A Section\n" + theToken.setSectionFormat(r"", False) + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H4: Section w/Format + theToken.theText = "#### A Section\n" + theToken.setSectionFormat(r"X: %title%", False) + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD4, 1, "X: A Section", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # H4: Section Separator + theToken.theText = "#### A Section\n" + theToken.setSectionFormat(r"* * *", False) + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + ] + + # Check the first scene detector + assert theToken.firstScene is False + theToken.firstScene = True + assert theToken.firstScene is True + theToken.theText = "Some text ...\n" + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.firstScene is False + + ## + # Title or Partition + ## + + theToken.isNovel = False + + # H1: Title + theToken.theText = "# Novel Title\n" + theToken.setTitleFormat(r"T: %title%") + theToken.tokenizeText() + theToken.isTitle = True + theToken.isPart = False + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_TITLE, 1, "Novel Title", None, Tokenizer.A_PBB_NO | Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_PBA | Tokenizer.A_CENTRE), + ] + + # H1: Partition + theToken.theText = "# Partition Title\n" + theToken.setTitleFormat(r"T: %title%") + theToken.tokenizeText() + theToken.isTitle = False + theToken.isPart = True + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_HEAD1, 1, "Partition Title", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_PBA | Tokenizer.A_CENTRE), + ] + + ## + # Page + ## + + theToken.isNovel = False + theToken.isTitle = False + theToken.isPart = False + theToken.isPage = True + + # Some Page Text + theToken.theText = "Page text\n\nMore text\n" + theToken.tokenizeText() + theToken.doHeaders() + assert theToken.theTokens == [ + (Tokenizer.T_TEXT, 1, "Page text", [], Tokenizer.A_PBB | Tokenizer.A_LEFT), + (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_LEFT), + (Tokenizer.T_TEXT, 3, "More text", [], Tokenizer.A_LEFT), + (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_LEFT), + ] + +# END Test testCoreToken_Headers From 91ece2b7f284e0644abdd9d5755b340e2c07e0d6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 4 Dec 2020 20:58:44 +0100 Subject: [PATCH 16/22] New test for ToHtml class --- nw/core/tohtml.py | 20 +- nw/gui/docviewer.py | 2 +- nw/guimain.py | 8 +- tests/dummy.py | 2 + tests/test_core_tohtml.py | 373 +++++++++++++++++++++++++++++++++++ tests/test_core_tokenizer.py | 3 +- 6 files changed, 392 insertions(+), 16 deletions(-) create mode 100644 tests/test_core_tohtml.py diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index df6c4a09..f3166d02 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -68,16 +68,16 @@ class ToHtml(Tokenizer): # Setters ## - def setPreview(self, forPreview, doComments, doSynopsis): + def setPreview(self, doComments, doSynopsis): """If we're using this class to generate markdown preview, we need to make a few changes to formatting, which is managed by these flags. """ - if forPreview: - self.genMode = self.M_PREVIEW - self.doKeywords = True - self.doComments = doComments - self.doSynopsis = doSynopsis + self.genMode = self.M_PREVIEW + self.doKeywords = True + self.doComments = doComments + self.doSynopsis = doSynopsis + return def setStyles(self, cssStyles): @@ -141,11 +141,13 @@ class ToHtml(Tokenizer): # For novel files for export, we bump the titles one level # up as this is more useful for printing and word processor # imports. - h1 = "h1 class='title'" + h1Cl = " class='title'" + h1 = "h1" h2 = "h1" h3 = "h2" h4 = "h3" else: + h1Cl = "" h1 = "h1" h2 = "h2" h3 = "h3" @@ -193,7 +195,7 @@ class ToHtml(Tokenizer): else: aNm = "" - # Process TextType + # Process Text Type if tType == self.T_EMPTY: if parStyle is None: parStyle = "" @@ -214,7 +216,7 @@ class ToHtml(Tokenizer): elif tType == self.T_HEAD1: tHead = tText.replace(r"\\", "
") - tmpResult.append("<%s%s>%s%s\n" % (h1, hStyle, aNm, tHead, h1)) + tmpResult.append("<%s%s%s>%s%s\n" % (h1, h1Cl, hStyle, aNm, tHead, h1)) elif tType == self.T_HEAD2: tHead = tText.replace(r"\\", "
") diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index e74e7ada..f3badad5 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -169,7 +169,7 @@ class GuiDocViewer(QTextBrowser): sPos = self.verticalScrollBar().value() aDoc = ToHtml(self.theProject, self.theParent) - aDoc.setPreview(True, self.mainConf.viewComments, self.mainConf.viewSynopsis) + aDoc.setPreview(self.mainConf.viewComments, self.mainConf.viewSynopsis) aDoc.setLinkHeaders(True) # Be extra careful here to prevent crashes when first opening a diff --git a/nw/guimain.py b/nw/guimain.py index 801de5af..76b344fb 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -81,14 +81,14 @@ class GuiMain(QMainWindow): # Core Classes # ============ - # Core Classes and settings + # Core Classes and Settings self.theTheme = GuiTheme(self) self.theProject = NWProject(self) self.theIndex = NWIndex(self.theProject, self) self.hasProject = False self.isFocusMode = False - # Prepare main window + # Prepare Main Window self.resize(*self.mainConf.getWinSize()) self._setWindowTitle() self.setWindowIcon(QIcon(self.mainConf.appIcon)) @@ -1073,10 +1073,10 @@ class GuiMain(QMainWindow): self.isFocusMode = not self.isFocusMode if self.isFocusMode: - logger.debug("Activating Focus mode") + logger.debug("Activating Focus Mode") self.tabWidget.setCurrentWidget(self.splitDocs) else: - logger.debug("Deactivating Focus mode") + logger.debug("Deactivating Focus Mode") isVisible = not self.isFocusMode self.treePane.setVisible(isVisible) diff --git a/tests/dummy.py b/tests/dummy.py index fbb91cbd..803e4e12 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -11,6 +11,8 @@ class DummyMain(): def __init__(self): self.mainConf = None self.hasProject = True + self.theIndex = None + self.theProject = None self.statusBar = StatusBar() return diff --git a/tests/test_core_tohtml.py b/tests/test_core_tohtml.py new file mode 100644 index 00000000..7c228edf --- /dev/null +++ b/tests/test_core_tohtml.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +"""novelWriter ToHtml Class Tester +""" + +import pytest + +from nw.core import NWProject, NWIndex, ToHtml + +@pytest.mark.core +def testCoreToHtml_Format(dummyGUI): + """Test all the formatters for the ToHtml class. + """ + theProject = NWProject(dummyGUI) + dummyGUI.theIndex = NWIndex(theProject, dummyGUI) + theHtml = ToHtml(theProject, dummyGUI) + + # Export Mode + # =========== + + assert theHtml._formatSynopsis("synopsis text") == ( + "

Synopsis: synopsis text

\n" + ) + assert theHtml._formatComments("comment text") == ( + "

Comment: comment text

\n" + ) + + assert theHtml._formatKeywords("") == "" + assert theHtml._formatKeywords("tag: Jane") == ( + "
" + ) + assert theHtml._formatKeywords("char: Bod, Jane") == ( + "
" + "Characters: " + "Bod, " + "Jane" + "
" + ) + + # Preview Mode + # ============ + + theHtml.setPreview(True, True) + + assert theHtml._formatSynopsis("synopsis text") == ( + "

Synopsis: synopsis text

\n" + ) + assert theHtml._formatComments("comment text") == ( + "

comment text

\n" + ) + + assert theHtml._formatKeywords("") == "" + assert theHtml._formatKeywords("tag: Jane") == ( + "
Tag: Jane
" + ) + assert theHtml._formatKeywords("char: Bod, Jane") == ( + "
" + "Characters: " + "Bod, " + "Jane" + "
" + ) + +# END Test testCoreToHtml_Format + +@pytest.mark.core +def testCoreToHtml_Convert(dummyGUI): + """Test the converter of the ToHtml class. + """ + theProject = NWProject(dummyGUI) + dummyGUI.theIndex = NWIndex(theProject, dummyGUI) + theHtml = ToHtml(theProject, dummyGUI) + + # Export Mode + # =========== + + theHtml.isNovel = True + + # Header 1 + theHtml.theText = "# Title\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Title

\n" + + # Header 2 + theHtml.theText = "## Chapter Title\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Chapter Title

\n" + + # Header 3 + theHtml.theText = "### Scene Title\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Scene Title

\n" + + # Header 4 + theHtml.theText = "#### Section Title\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Section Title

\n" + + theHtml.isNovel = False + theHtml.setLinkHeaders(True) + + # Header 1 + theHtml.theText = "# Heading One\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Heading One

\n" + + # Header 2 + theHtml.theText = "## Heading Two\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Heading Two

\n" + + # Header 3 + theHtml.theText = "### Heading Three\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Heading Three

\n" + + # Header 4 + theHtml.theText = "#### Heading Four\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "

Heading Four

\n" + + # Text + theHtml.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "

Some nested bold and italic and " + "strikethrough text here

\n" + ) + + # Text w/Hard Break + theHtml.theText = "Line one \nLine two \nLine three\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "

Line one
Line two
Line three

\n" + ) + + # Synopsis + theHtml.theText = "%synopsis: The synopsis ...\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "" + + theHtml.setSynopsis(True) + theHtml.theText = "%synopsis: The synopsis ...\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "

Synopsis: The synopsis ...

\n" + ) + + # Comment + theHtml.theText = "% A comment ...\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "" + + theHtml.setComments(True) + theHtml.theText = "% A comment ...\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "

Comment: A comment ...

\n" + ) + + # Keywords + theHtml.theText = "@char: Bod, Jane\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == "" + + theHtml.setKeywords(True) + theHtml.theText = "@char: Bod, Jane\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "
Characters: " + "Bod, Jane
" + ) + + # Direct Tests + # ============ + + theHtml.isNovel = True + + # Title + theHtml.theTokens = [ + (theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB_NO | theHtml.A_CENTRE), + (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

" + "A Title

\n" + ) + + # Separator + theHtml.theTokens = [ + (theHtml.T_SEP, 1, "* * *", None, theHtml.A_CENTRE), + (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + ] + theHtml.doConvert() + assert theHtml.theResult == "

* * *

\n" + + # Skip + theHtml.theTokens = [ + (theHtml.T_SKIP, 1, "", None, theHtml.A_NONE), + (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + ] + theHtml.doConvert() + assert theHtml.theResult == "

 

\n" + + # Styles + # ====== + + theHtml.setLinkHeaders(False) + + # Align Left + theHtml.setStyles(False) + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + theHtml.setStyles(True) + + # Align Left + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Align Right + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_RIGHT), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Align Centre + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_CENTRE), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Align Justify + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_JUSTIFY), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Page Break Always + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB | theHtml.A_PBA), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Page Break Avoid + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_AV | theHtml.A_PBA_AV), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Page Break ANever + theHtml.theTokens = [ + (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_NO | theHtml.A_PBA_NO), + ] + theHtml.doConvert() + assert theHtml.theResult == ( + "

A Title

\n" + ) + + # Preview Mode + # ============ + + theHtml.setPreview(True, True) + + # Text (HTML4) + theHtml.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "

Some nested bold and italic and " + "strikethrough " + "text here

\n" + ) + +# END Test testCoreToHtml_Convert + +@pytest.mark.core +def testCoreToHtml_Methods(dummyGUI): + """Test all the other methods of the ToHtml class. + """ + theProject = NWProject(dummyGUI) + theHtml = ToHtml(theProject, dummyGUI) + + # Auto-Replace + docText = "Text with & short–dash, long—dash …\n" + theHtml.theText = docText + theHtml.doAutoReplace() + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theResult == ( + "

Text with <brackets> & short–dash, long—dash …

\n" + ) + + # Revert on MD + assert theHtml.theMarkdown == ( + "Text with <brackets> & short–dash, long—dash …\n\n" + ) + theHtml.doPostProcessing() + assert theHtml.theMarkdown == docText + "\n" + + # With Preview, No Revert + theHtml.setPreview(True, True) + theHtml.theText = docText + theHtml.doAutoReplace() + theHtml.tokenizeText() + theHtml.doConvert() + assert theHtml.theMarkdown == ( + "Text with <brackets> & short–dash, long—dash …\n\n" + ) + theHtml.doPostProcessing() + assert theHtml.theMarkdown == ( + "Text with <brackets> & short–dash, long—dash …\n\n" + ) + + # CSS + # === + + assert len(theHtml.getStyleSheet()) > 1 + assert "p {text-align: left;}" in theHtml.getStyleSheet() + assert "p {text-align: justify;}" not in theHtml.getStyleSheet() + + theHtml.setJustify(True) + assert "p {text-align: left;}" not in theHtml.getStyleSheet() + assert "p {text-align: justify;}" in theHtml.getStyleSheet() + + theHtml.setStyles(False) + assert theHtml.getStyleSheet() == [] + +# END Test testCoreToHtml_Methods diff --git a/tests/test_core_tokenizer.py b/tests/test_core_tokenizer.py index 559f3a1e..3d0774b5 100644 --- a/tests/test_core_tokenizer.py +++ b/tests/test_core_tokenizer.py @@ -12,7 +12,7 @@ def testCoreToken_Setters(dummyGUI): """Test all the setters for the Tokenizer class. """ theProject = NWProject(dummyGUI) - theToken = Tokenizer(dummyGUI, theProject) + theToken = Tokenizer(theProject, dummyGUI) # Verify defaults assert theToken.fmtTitle == "%title%" @@ -600,7 +600,6 @@ def testCoreToken_Headers(dummyGUI): # H1: Title theToken.theText = "# Novel Title\n" - theToken.setTitleFormat(r"T: %title%") theToken.tokenizeText() theToken.isTitle = True theToken.isPart = False From 57397fa2bcf419c83de8989cfa8a3eb72198059c Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 4 Dec 2020 21:28:35 +0100 Subject: [PATCH 17/22] Updated existing NWProject tests --- pytest.ini | 1 - tests/README.md | 1 + tests/conftest.py | 36 ++--- ...roject.nwx => coreProject_1_nwProject.nwx} | 0 ...roject.nwx => coreProject_2_nwProject.nwx} | 0 ...roject.nwx => coreProject_3_nwProject.nwx} | 0 ...roject.nwx => coreProject_4_nwProject.nwx} | 0 ...roject.nwx => coreProject_5_nwProject.nwx} | 0 .../{test_project.py => test_core_project.py} | 150 ++++++++++-------- 9 files changed, 101 insertions(+), 87 deletions(-) rename tests/reference/{proj/1_nwProject.nwx => coreProject_1_nwProject.nwx} (100%) rename tests/reference/{proj/2_nwProject.nwx => coreProject_2_nwProject.nwx} (100%) rename tests/reference/{proj/3_nwProject.nwx => coreProject_3_nwProject.nwx} (100%) rename tests/reference/{proj/4_nwProject.nwx => coreProject_4_nwProject.nwx} (100%) rename tests/reference/{proj/5_nwProject.nwx => coreProject_5_nwProject.nwx} (100%) rename tests/{test_project.py => test_core_project.py} (82%) diff --git a/pytest.ini b/pytest.ini index 0c3af3ba..b61d59ec 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,5 @@ [pytest] markers = - project: Project classes tests error: Test various error handling scenarios core: Core functionality tests gui: Qt5 GUI tests diff --git a/tests/README.md b/tests/README.md index b9c221f9..82a34712 100644 --- a/tests/README.md +++ b/tests/README.md @@ -69,4 +69,5 @@ The commands for the respective test categories are listed below. | Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` | | Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | | Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` | +| Unit | ToHtml class | nw/core/tohtml.py | `-m core` | `-k testCoreToHtml` | | Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` | diff --git a/tests/conftest.py b/tests/conftest.py index 3a97c58f..e6b90511 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,20 @@ def outDir(tmpDir): os.mkdir(theDir) return theDir +@pytest.fixture(scope="function") +def fncDir(tmpDir): + """A temporary folder for a single test function. + """ + funcDir = os.path.join(tmpDir, "ftemp") + if os.path.isdir(funcDir): + shutil.rmtree(funcDir) + if not os.path.isdir(funcDir): + os.mkdir(funcDir) + yield funcDir + if os.path.isdir(funcDir): + shutil.rmtree(funcDir) + return + ## # novelWriter Objects ## @@ -158,32 +172,10 @@ def yesToAll(monkeypatch): # =============================================================================================== # -## -# novelWriter Objects -## - -@pytest.fixture(scope="session") -def nwConf(refDir, tmpDir): - """Temporary novelWriter configuration used for the dummy instance - of novelWriter's main GUI. - """ - theConf = Config() - theConf.initConfig(refDir, tmpDir) - return theConf - ## # Temporary Test Folders ## -@pytest.fixture(scope="session") -def nwTempProj(tmpDir): - """A temporary folder for project tests. - """ - projDir = os.path.join(tmpDir, "proj") - if not os.path.isdir(projDir): - os.mkdir(projDir) - return projDir - @pytest.fixture(scope="session") def nwTempGUI(tmpDir): """A temporary folder for GUI tests. diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/coreProject_1_nwProject.nwx similarity index 100% rename from tests/reference/proj/1_nwProject.nwx rename to tests/reference/coreProject_1_nwProject.nwx diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/coreProject_2_nwProject.nwx similarity index 100% rename from tests/reference/proj/2_nwProject.nwx rename to tests/reference/coreProject_2_nwProject.nwx diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/coreProject_3_nwProject.nwx similarity index 100% rename from tests/reference/proj/3_nwProject.nwx rename to tests/reference/coreProject_3_nwProject.nwx diff --git a/tests/reference/proj/4_nwProject.nwx b/tests/reference/coreProject_4_nwProject.nwx similarity index 100% rename from tests/reference/proj/4_nwProject.nwx rename to tests/reference/coreProject_4_nwProject.nwx diff --git a/tests/reference/proj/5_nwProject.nwx b/tests/reference/coreProject_5_nwProject.nwx similarity index 100% rename from tests/reference/proj/5_nwProject.nwx rename to tests/reference/coreProject_5_nwProject.nwx diff --git a/tests/test_project.py b/tests/test_core_project.py similarity index 82% rename from tests/test_project.py rename to tests/test_core_project.py index 748021a7..6cf20033 100644 --- a/tests/test_project.py +++ b/tests/test_core_project.py @@ -13,13 +13,13 @@ from tools import cmpFiles from nw.core.project import NWProject from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles -@pytest.mark.project -def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI): - """Test that a basic project can be created, and opened and saved. +@pytest.mark.core +def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI): + """Test that a basic project can be created, opened and saved. """ - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") - testFile = os.path.join(nwTempProj, "1_nwProject.nwx") - refFile = os.path.join(refDir, "proj", "1_nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_1_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -28,17 +28,17 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI): assert not theProject.newProject({}) # Try again with a proper path - assert theProject.newProject({"projPath": nwFuncTemp}) - assert theProject.setProjectPath(nwFuncTemp) + assert theProject.newProject({"projPath": fncDir}) + assert theProject.setProjectPath(fncDir) assert theProject.saveProject() assert theProject.closeProject() # Creating the project once more should fail - assert not theProject.newProject({"projPath": nwFuncTemp}) + assert not theProject.newProject({"projPath": fncDir}) # Check the new project copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) # Open again assert theProject.openProject(projFile) @@ -47,7 +47,7 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI): assert theProject.saveProject() assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert not theProject.projChanged # Open a second time @@ -57,21 +57,23 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI): assert theProject.saveProject() assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) -@pytest.mark.project -def testProjectNewRoot(nwFuncTemp, nwTempProj, refDir, dummyGUI): +# END Test testCoreProject_NewOpenSave + +@pytest.mark.core +def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI): """Check that new root folders can be added to the project. """ - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") - testFile = os.path.join(nwTempProj, "2_nwProject.nwx") - refFile = os.path.join(refDir, "proj", "2_nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) - assert theProject.newProject({"projPath": nwFuncTemp}) - assert theProject.setProjectPath(nwFuncTemp) + assert theProject.newProject({"projPath": fncDir}) + assert theProject.setProjectPath(fncDir) assert theProject.saveProject() assert theProject.closeProject() assert theProject.openProject(projFile) @@ -90,22 +92,24 @@ def testProjectNewRoot(nwFuncTemp, nwTempProj, refDir, dummyGUI): assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert not theProject.projChanged -@pytest.mark.project -def testProjectNewFile(nwFuncTemp, nwTempProj, refDir, dummyGUI): +# END Test testCoreProject_NewRoot + +@pytest.mark.core +def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI): """Check that new files can be added to the project. """ - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") - testFile = os.path.join(nwTempProj, "3_nwProject.nwx") - refFile = os.path.join(refDir, "proj", "3_nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) - assert theProject.newProject({"projPath": nwFuncTemp}) - assert theProject.setProjectPath(nwFuncTemp) + assert theProject.newProject({"projPath": fncDir}) + assert theProject.setProjectPath(fncDir) assert theProject.saveProject() assert theProject.closeProject() assert theProject.openProject(projFile) @@ -117,23 +121,25 @@ def testProjectNewFile(nwFuncTemp, nwTempProj, refDir, dummyGUI): assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert not theProject.projChanged -@pytest.mark.project -def testProjectNewCustomA(nwFuncTemp, nwTempProj, refDir, dummyGUI): +# END Test testCoreProject_NewFile + +@pytest.mark.core +def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): """Create a new project from a project wizard dictionary. Custom type with chapters and scenes. """ - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") - testFile = os.path.join(nwTempProj, "4_nwProject.nwx") - refFile = os.path.join(refDir, "proj", "4_nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx") projData = { "projName": "Test Custom", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": nwFuncTemp, + "projPath": fncDir, "popSample": False, "popMinimal": False, "popCustom": True, @@ -157,22 +163,24 @@ def testProjectNewCustomA(nwFuncTemp, nwTempProj, refDir, dummyGUI): assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) -@pytest.mark.project -def testProjectNewCustomB(nwFuncTemp, nwTempProj, refDir, dummyGUI): +# END Test testCoreProject_NewCustomA + +@pytest.mark.core +def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI): """Create a new project from a project wizard dictionary. Custom type without chapters, but with scenes. """ - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") - testFile = os.path.join(nwTempProj, "5_nwProject.nwx") - refFile = os.path.join(refDir, "proj", "5_nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx") projData = { "projName": "Test Custom", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": nwFuncTemp, + "projPath": fncDir, "popSample": False, "popMinimal": False, "popCustom": True, @@ -196,10 +204,12 @@ def testProjectNewCustomB(nwFuncTemp, nwTempProj, refDir, dummyGUI): assert theProject.closeProject() copyfile(projFile, testFile) - assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) -@pytest.mark.project -def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir): +# END Test testCoreProject_NewCustomB + +@pytest.mark.core +def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir): """Check that we can create a new project can be created from the provided sample project via a zip file. """ @@ -207,22 +217,22 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir): "projName": "Test Sample", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": nwFuncTemp, + "projPath": fncDir, "popSample": True, "popMinimal": False, "popCustom": False, } theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) - theProject.mainConf = nwConf + theProject.mainConf = tmpConf # Sample set, but no path assert not theProject.newProject({"popSample": True}) # Force the lookup path for assets to our temp folder - srcSample = os.path.abspath(os.path.join(nwConf.appRoot, "sample")) + srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample")) dstSample = os.path.join(tmpDir, "sample.zip") - nwConf.assetPath = tmpDir + tmpConf.assetPath = tmpDir # Create and open a defective zip file with open(dstSample, mode="w+") as outFile: @@ -239,14 +249,16 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir): zipObj.write(srcDoc, "content/"+docFile) assert theProject.newProject(projData) - assert theProject.openProject(nwFuncTemp) + assert theProject.openProject(fncDir) assert theProject.projName == "Sample Project" assert theProject.saveProject() assert theProject.closeProject() os.unlink(dstSample) -@pytest.mark.project -def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir): +# END Test testCoreProject_NewSampleA + +@pytest.mark.core +def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir): """Check that we can create a new project can be created from the provided sample project folder. """ @@ -254,17 +266,17 @@ def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir): "projName": "Test Sample", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": nwFuncTemp, + "projPath": fncDir, "popSample": True, "popMinimal": False, "popCustom": False, } theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) - theProject.mainConf = nwConf + theProject.mainConf = tmpConf # Make sure we do not pick up the nw/assets/sample.zip file - nwConf.assetPath = tmpDir + tmpConf.assetPath = tmpDir # Set a fake project file name monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx") @@ -272,17 +284,19 @@ def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir): monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx") assert theProject.newProject(projData) - assert theProject.openProject(nwFuncTemp) + assert theProject.openProject(fncDir) assert theProject.projName == "Sample Project" assert theProject.saveProject() assert theProject.closeProject() # Misdirect the appRoot path so neither is possible - nwConf.appRoot = tmpDir + tmpConf.appRoot = tmpDir assert not theProject.newProject(projData) -@pytest.mark.project -def testProjectMethods(monkeypatch, nwMinimal, dummyGUI): +# END Test testCoreProject_NewSampleB + +@pytest.mark.core +def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI): """Test other project class methods and functions. """ theProject = NWProject(dummyGUI) @@ -323,8 +337,10 @@ def testProjectMethods(monkeypatch, nwMinimal, dummyGUI): assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.bookAuthors == ["Jane Doe", "John Doh"] -@pytest.mark.project -def testProjectOrphanedFiles(dummyGUI, nwLipsum): +# END Test testCoreProject_Methods + +@pytest.mark.core +def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum): """Check that files in the content folder that are not tracked in the project XML file are handled correctly by the orphaned files function. It should also restore as much meta data as possible from @@ -392,8 +408,10 @@ def testProjectOrphanedFiles(dummyGUI, nwLipsum): assert theProject.saveProject(nwLipsum) assert theProject.closeProject() -@pytest.mark.project -def testProjectOldFormat(dummyGUI, nwOldProj): +# END Test testCoreProject_OrphanedFiles + +@pytest.mark.core +def testCoreProject_OldFormat(dummyGUI, nwOldProj): """Test that a project folder structure of version 1.0 can be converted to the latest folder structure. Version 1.0 split the documents into 'data_0' ... 'data_f' folders, which are now all @@ -482,8 +500,10 @@ def testProjectOldFormat(dummyGUI, nwOldProj): assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionStats.log")) assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt")) -@pytest.mark.project -def testProjectBackup(dummyGUI, nwMinimal, tmpDir): +# END Test testCoreProject_OldFormat + +@pytest.mark.core +def testCoreProject_Backup(dummyGUI, nwMinimal, 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 @@ -530,3 +550,5 @@ def testProjectBackup(dummyGUI, nwMinimal, tmpDir): assert cmpFiles( os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx") ) + +# END Test testCoreProject_Backup From c17d4de0a5984be468006d7e2dec2bfae5789af0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 5 Dec 2020 19:13:36 +0100 Subject: [PATCH 18/22] Remove QMessageBox dependency in NWProject class --- nw/core/project.py | 86 +++++++++++++++++++++------------------------- nw/core/status.py | 3 +- nw/guimain.py | 7 ++++ 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index f8e45194..b646934a 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -28,12 +28,10 @@ import nw import logging import os +import shutil from lxml import etree from time import time -from shutil import make_archive, unpack_archive, copyfile - -from PyQt5.QtWidgets import QMessageBox from nw.core.tree import NWTree from nw.core.item import NWItem @@ -436,26 +434,15 @@ class NWProject(): xRoot = nwXML.getroot() nwxRoot = xRoot.tag - appVersion = "Unknown" - hexVersion = "0x0" - fileVersion = "Unknown" - self.saveCount = 0 - self.autoCount = 0 - - if "appVersion" in xRoot.attrib: - appVersion = xRoot.attrib["appVersion"] - if "hexVersion" in xRoot.attrib: - hexVersion = xRoot.attrib["hexVersion"] - if "fileVersion" in xRoot.attrib: - fileVersion = xRoot.attrib["fileVersion"] + appVersion = xRoot.attrib.get("appVersion", "Unknown") + hexVersion = xRoot.attrib.get("hexVersion", "0x0") + fileVersion = xRoot.attrib.get("fileVersion", "Unknown") # The following are deprecated and will be removed - if "saveCount" in xRoot.attrib: - self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False) - if "autoCount" in xRoot.attrib: - self.autoCount = checkInt(xRoot.attrib["autoCount"], 0, False) - if "editTime" in xRoot.attrib: - self.editTime = checkInt(xRoot.attrib["editTime"], 0, False) + # The settings have been moved to the tag + self.saveCount = checkInt(xRoot.attrib.get("saveCount", 0), 0, False) + self.autoCount = checkInt(xRoot.attrib.get("autoCount", 0), 0, False) + self.editTime = checkInt(xRoot.attrib.get("editTime", 0), 0, False) logger.verbose("XML root is %s" % nwxRoot) logger.verbose("File version is %s" % fileVersion) @@ -483,20 +470,19 @@ class NWProject(): # parser will lose the autoReplace settings if allowed to # read the file. Introduced in version 0.10. - if fileVersion == "1.0" and self.mainConf.showGUI: - msgBox = QMessageBox() - msgRes = msgBox.question(self.theParent, "Old Project Version", ( + if fileVersion == "1.0": + msgRes = self.theParent.askQuestion("Old Project Version", ( "The project file and data is created by a novelWriter version " "lower than 0.7. Do you want to upgrade the project to the " "most recent format?

Note that after the upgrade, you " "cannot open the project with an older version of novelWriter " "any more, so make sure you have a recent backup." )) - if msgRes != QMessageBox.Yes: + if not msgRes: self.clearProject() return False - elif fileVersion != "1.1" and fileVersion != "1.2" and self.mainConf.showGUI: + elif fileVersion != "1.1" and fileVersion != "1.2": self.makeAlert(( "Unknown or unsupported novelWriter project file format. " "The project cannot be opened by this version of novelWriter. " @@ -510,9 +496,8 @@ class NWProject(): # Check novelWriter Version # ========================= - if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI: - msgBox = QMessageBox() - msgRes = msgBox.question(self.theParent, "Version Conflict", ( + if int(hexVersion, 16) > int(nw.__hexversion__, 16): + msgRes = self.theParent.askQuestion("Version Conflict", ( "This project was saved by a newer version of novelWriter, version %s. " "This is version %s. If you continue to open the project, some attributes " "and settings may not be preserved, but the overall project should be fine. " @@ -520,7 +505,7 @@ class NWProject(): ) % ( appVersion, nw.__version__ )) - if msgRes != QMessageBox.Yes: + if not msgRes: self.clearProject() return False @@ -835,15 +820,15 @@ class NWProject(): try: self._clearLockFile() - make_archive(baseName, "zip", self.projPath, ".") + shutil.make_archive(baseName, "zip", self.projPath, ".") self._writeLockFile() + logger.info("Backup written to: %s" % archName) if doNotify: self.theParent.makeAlert( "Backup archive file written to: %s.zip" % os.path.join(cleanName, archName), nwAlert.INFO ) - else: - logger.info("Backup written to: %s" % archName) + except Exception as e: self.theParent.makeAlert( ["Could not write backup archive.", str(e)], @@ -874,7 +859,7 @@ class NWProject(): self.setProjectPath(projPath, newProject=True) try: - unpack_archive(pkgSample, projPath) + shutil.unpack_archive(pkgSample, projPath) isSuccess = True except Exception as e: self.makeAlert( @@ -887,14 +872,14 @@ class NWProject(): try: srcProj = os.path.join(srcSample, nwFiles.PROJ_FILE) dstProj = os.path.join(projPath, nwFiles.PROJ_FILE) - copyfile(srcProj, dstProj) + shutil.copyfile(srcProj, dstProj) srcContent = os.path.join(srcSample, "content") dstContent = os.path.join(projPath, "content") for srcFile in os.listdir(srcContent): srcDoc = os.path.join(srcContent, srcFile) dstDoc = os.path.join(dstContent, srcFile) - copyfile(srcDoc, dstDoc) + shutil.copyfile(srcDoc, dstDoc) isSuccess = True @@ -998,11 +983,15 @@ class NWProject(): "You must set a valid backup path in preferences to use " "the automatic project backup feature." ), nwAlert.WARN) + return False + if self.projName == "": self.theParent.makeAlert(( "You must set a valid project name in project settings to " "use the automatic project backup feature." ), nwAlert.WARN) + return False + return True def setSpellCheck(self, theMode): @@ -1011,12 +1000,15 @@ class NWProject(): if self.spellCheck != theMode: self.spellCheck = theMode self.setProjectChanged(True) - return True + return self.spellCheck def setSpellLang(self, theLang): """Set the project-specific spell check language. """ - self.projLang = checkString(theLang, None, True) + theLang = checkString(theLang, None, True) + if self.projLang != theLang: + self.projLang = theLang + self.setProjectChanged(True) return True def setAutoOutline(self, theMode): @@ -1025,7 +1017,7 @@ class NWProject(): if self.autoOutline != theMode: self.autoOutline = theMode self.setProjectChanged(True) - return True + return self.autoOutline def setTreeOrder(self, newOrder): """A list representing the linear/flattened order of project @@ -1072,7 +1064,7 @@ class NWProject(): if nwItem.itemStatus in replaceMap.keys(): nwItem.setStatus(replaceMap[nwItem.itemStatus]) self.setProjectChanged(True) - return + return True def setImportColours(self, newCols): """Update the list of note file importance flags. Also iterate @@ -1084,14 +1076,15 @@ class NWProject(): if nwItem.itemStatus in replaceMap.keys(): nwItem.setStatus(replaceMap[nwItem.itemStatus]) self.setProjectChanged(True) - return + return True def setAutoReplace(self, autoReplace): """Update the auto-replace dictionary. This replaces the entire dictionary, so alterations have to be made in a copy. """ self.autoReplace = autoReplace - return + self.setProjectChanged(True) + return True def setTitleFormat(self, titleFormat): """Set the formatting of titles in the project. @@ -1099,7 +1092,7 @@ class NWProject(): for valKey, valEntry in titleFormat.items(): if valKey in self.titleFormat: self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey], False) - return + return True def setProjectChanged(self, bValue): """Toggle the project changed flag, and propagate the @@ -1292,7 +1285,7 @@ class NWProject(): back into the project tree. """ if self.projPath is None: - return + return False # Then check the files in the data folder logger.debug("Checking files in project content folder") @@ -1352,7 +1345,7 @@ class NWProject(): orphItem.setLayout(oLayout) self.projTree.append(oHandle, None, orphItem) - return + return True def _appendSessionStats(self): """Append session statistics to the sessions log file. @@ -1490,7 +1483,8 @@ class NWProject(): os.unlink(rmFile) except Exception as e: logger.error(str(e)) + return False - return + return True # END Class NWProject diff --git a/nw/core/status.py b/nw/core/status.py index 2c6fe811..ccec2e73 100644 --- a/nw/core/status.py +++ b/nw/core/status.py @@ -109,7 +109,8 @@ class NWStatus(): return def countEntry(self, theLabel): - """Lookup the usage count of a given entry. + """Increment the counter for a given label. This should be used + together with resetCounts in a loop over project items. """ theIndex = self.lookupEntry(theLabel) if theIndex is not None: diff --git a/nw/guimain.py b/nw/guimain.py index 76b344fb..9efc9a2e 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -982,6 +982,13 @@ class GuiMain(QMainWindow): return + def askQuestion(self, theTitle, theQuestion): + """Ask the user a Yes/No question. + """ + msgBox = QMessageBox() + msgRes = msgBox.question(self, theTitle, theQuestion) + return msgRes == QMessageBox.Yes + def reportConfErr(self): """Checks if the Config module has any errors to report, and let the user know if this is the case. The Config module caches From c353e9ffb121196fdfffaee270454296a799073b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 5 Dec 2020 21:06:57 +0100 Subject: [PATCH 19/22] Updated and new tests for NWProject class --- nw/core/project.py | 51 +- tests/README.md | 1 + tests/conftest.py | 9 +- tests/dummy.py | 20 + tests/reference/coreProject_2_nwProject.nwx | 189 ++++- tests/reference/coreProject_3_nwProject.nwx | 108 ++- tests/reference/coreProject_4_nwProject.nwx | 189 +---- tests/reference/coreProject_5_nwProject.nwx | 108 +-- tests/test_core_project.py | 832 ++++++++++++++++++-- tests/test_core_tree.py | 6 +- 10 files changed, 1101 insertions(+), 412 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index b646934a..1a834d73 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -1125,13 +1125,11 @@ class NWProject(): sentItems = [] iterItems = self.projTree.handles() n = 0 - nMax = len(iterItems) + nMax = min(len(iterItems), 10000) while n < nMax: tHandle = iterItems[n] tItem = self.projTree[tHandle] n += 1 - if n > 10000: - return # Just in case if tItem is None: # Technically a bug since treeOrder is built from the # same data as projTree @@ -1147,10 +1145,11 @@ class NWProject(): yield tItem elif tItem.itemParent in iterItems: # Item's parent exists, but hasn't been sent yet, so add - # it again to the end + # it again to the end, but make sure this doesn't get + # out hand, so we cap at 10000 items logger.warning("Item %s found before its parent" % tHandle) iterItems.append(tHandle) - nMax = len(iterItems) + nMax = min(len(iterItems), 10000) else: # Item is orphaned logger.error("Item %s has no parent in current tree" % tHandle) @@ -1189,13 +1188,12 @@ class NWProject(): if not os.path.isfile(lockFile): return [] + theLines = [] try: with open(lockFile, mode="r", encoding="utf8") as inFile: theData = inFile.read() theLines = theData.splitlines() - if len(theLines) == 4: - return theLines - else: + if len(theLines) != 4: return ["ERROR"] except Exception as e: @@ -1203,7 +1201,7 @@ class NWProject(): logger.error(str(e)) return ["ERROR"] - return ["ERROR"] + return theLines def _writeLockFile(self): """Writes a lock file to the project folder. @@ -1236,13 +1234,12 @@ class NWProject(): if os.path.isfile(lockFile): try: os.unlink(lockFile) - return True except Exception as e: logger.error("Failed to remove project lockfile") logger.error(str(e)) return False - return None + return True def _checkFolder(self, thePath): """Check if a folder exists, and if it doesn't, create it. @@ -1356,21 +1353,27 @@ class NWProject(): sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS) isFile = os.path.isfile(sessionFile) - with open(sessionFile, mode="a+", encoding="utf8") as outFile: - if not isFile: - # It's a new file, so add a header - if self.lastWCount > 0: - outFile.write("# Offset %d\n" % self.lastWCount) - outFile.write("# %-17s %-19s %8s %8s\n" % ( - "Start Time", "End Time", "Novel", "Notes" + try: + with open(sessionFile, mode="a+", encoding="utf8") as outFile: + if not isFile: + # It's a new file, so add a header + if self.lastWCount > 0: + outFile.write("# Offset %d\n" % self.lastWCount) + outFile.write("# %-17s %-19s %8s %8s\n" % ( + "Start Time", "End Time", "Novel", "Notes" + )) + + outFile.write("%-19s %-19s %8d %8d\n" % ( + formatTimeStamp(self.projOpened), + formatTimeStamp(time()), + self.novelWCount, + self.notesWCount, )) - outFile.write("%-19s %-19s %8d %8d\n" % ( - formatTimeStamp(self.projOpened), - formatTimeStamp(time()), - self.novelWCount, - self.notesWCount, - )) + except Exception as e: + logger.error("Failed to write session stats file") + logger.error(str(e)) + return False return True diff --git a/tests/README.md b/tests/README.md index 82a34712..cd1be55b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -65,6 +65,7 @@ The commands for the respective test categories are listed below. | Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | | Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | | Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | +| Unit | NWProject class | nw/core/project.py | `-m core` | `-k testCoreProject` | | Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` | | Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` | | Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | diff --git a/tests/conftest.py b/tests/conftest.py index e6b90511..1f7b88ae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,12 +6,15 @@ import sys import pytest import shutil import os +import time from dummy import DummyMain from PyQt5.QtWidgets import QMessageBox sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) +os.environ["TZ"] = "UTC" +time.tzset() from nw.config import Config # noqa: E402 @@ -69,7 +72,7 @@ def fncDir(tmpDir): # novelWriter Objects ## -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def tmpConf(tmpDir): """Create a temporary novelWriter configuration object. """ @@ -78,7 +81,7 @@ def tmpConf(tmpDir): theConf.setLastPath("") return theConf -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def dummyGUI(tmpConf): """Create a dummy instance of novelWriter's main GUI class. """ @@ -168,6 +171,8 @@ def yesToAll(monkeypatch): monkeypatch.setattr( QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes ) + yield + monkeypatch.undo() return # =============================================================================================== # diff --git a/tests/dummy.py b/tests/dummy.py index 803e4e12..0f26e0ff 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -14,12 +14,22 @@ class DummyMain(): self.theIndex = None self.theProject = None self.statusBar = StatusBar() + + # Test Variables + self.askResponse = True + self.lastAlert = "" + return def makeAlert(self, theMessage, theLevel): print("%s: %s" % (str(theLevel), theMessage)) + self.lastAlert = str(theMessage) return + def askQuestion(self, theTitle, theQustion): + print("Question: %s" % theQustion) + return self.askResponse + def setStatus(self, theMessage): return @@ -32,6 +42,16 @@ class DummyMain(): def rebuildIndex(self): return + # Test Functions + + def undo(self): + self.askResponse = True + return + + def clear(self): + self.lastAlert = "" + return + # END Class GuiMain class StatusBar(): diff --git a/tests/reference/coreProject_2_nwProject.nwx b/tests/reference/coreProject_2_nwProject.nwx index 9ea0617f..fc7e7ab2 100644 --- a/tests/reference/coreProject_2_nwProject.nwx +++ b/tests/reference/coreProject_2_nwProject.nwx @@ -1,9 +1,11 @@ - New Project - - 2 + Test Custom + Test Novel + Jane Doe + John Doh + 1 1 0 @@ -38,7 +40,7 @@ Main - + Novel ROOT @@ -61,13 +63,34 @@ False - World + Locations ROOT WORLD New False - + + Timeline + ROOT + TIMELINE + New + False + + + Objects + ROOT + OBJECT + New + False + + + Entity + ROOT + ENTITY + New + False + + Title Page FILE NOVEL @@ -79,15 +102,15 @@ 0 0 - - New Chapter + + Chapter 1 FOLDER NOVEL New False - - New Chapter + + Chapter 1 FILE NOVEL New @@ -98,8 +121,8 @@ 0 0 - - New Scene + + Scene 1.1 FILE NOVEL New @@ -110,33 +133,139 @@ 0 0 - - Timeline - ROOT - TIMELINE + + Scene 1.2 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 1.3 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Chapter 2 + FOLDER + NOVEL New False - - Object - ROOT - OBJECT + + Chapter 2 + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + + Scene 2.1 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 2.2 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 2.3 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Chapter 3 + FOLDER + NOVEL New False - - Custom1 - ROOT - CUSTOM + + Chapter 3 + FILE + NOVEL New - False + True + CHAPTER + 0 + 0 + 0 + 0 - - Custom2 - ROOT - CUSTOM + + Scene 3.1 + FILE + NOVEL New - False + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 3.2 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 3.3 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 diff --git a/tests/reference/coreProject_3_nwProject.nwx b/tests/reference/coreProject_3_nwProject.nwx index 66488434..11d007aa 100644 --- a/tests/reference/coreProject_3_nwProject.nwx +++ b/tests/reference/coreProject_3_nwProject.nwx @@ -1,9 +1,11 @@ - + - New Project - - 2 + Test Custom + Test Novel + Jane Doe + John Doh + 1 1 0 @@ -38,7 +40,7 @@ Main - + Novel ROOT @@ -61,13 +63,34 @@ False - World + Locations ROOT WORLD New False - + + Timeline + ROOT + TIMELINE + New + False + + + Objects + ROOT + OBJECT + New + False + + + Entity + ROOT + ENTITY + New + False + + Title Page FILE NOVEL @@ -79,27 +102,8 @@ 0 0 - - New Chapter - FOLDER - NOVEL - New - False - - - New Chapter - FILE - NOVEL - New - True - CHAPTER - 0 - 0 - 0 - 0 - - - New Scene + + Scene 1 FILE NOVEL New @@ -110,8 +114,8 @@ 0 0 - - Hello + + Scene 2 FILE NOVEL New @@ -122,13 +126,49 @@ 0 0 - - Jane + + Scene 3 FILE - CHARACTER + NOVEL New True - NOTE + SCENE + 0 + 0 + 0 + 0 + + + Scene 4 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 5 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 6 + FILE + NOVEL + New + True + SCENE 0 0 0 diff --git a/tests/reference/coreProject_4_nwProject.nwx b/tests/reference/coreProject_4_nwProject.nwx index fc7e7ab2..9ea0617f 100644 --- a/tests/reference/coreProject_4_nwProject.nwx +++ b/tests/reference/coreProject_4_nwProject.nwx @@ -1,11 +1,9 @@ - Test Custom - Test Novel - Jane Doe - John Doh - 1 + New Project + + 2 1 0 @@ -40,7 +38,7 @@ Main - + Novel ROOT @@ -63,34 +61,13 @@ False - Locations + World ROOT WORLD New False - - Timeline - ROOT - TIMELINE - New - False - - - Objects - ROOT - OBJECT - New - False - - - Entity - ROOT - ENTITY - New - False - - + Title Page FILE NOVEL @@ -102,15 +79,15 @@ 0 0 - - Chapter 1 + + New Chapter FOLDER NOVEL New False - - Chapter 1 + + New Chapter FILE NOVEL New @@ -121,8 +98,8 @@ 0 0 - - Scene 1.1 + + New Scene FILE NOVEL New @@ -133,139 +110,33 @@ 0 0 - - Scene 1.2 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 1.3 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Chapter 2 - FOLDER - NOVEL + + Timeline + ROOT + TIMELINE New False - - Chapter 2 - FILE - NOVEL - New - True - CHAPTER - 0 - 0 - 0 - 0 - - - Scene 2.1 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 2.2 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 2.3 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Chapter 3 - FOLDER - NOVEL + + Object + ROOT + OBJECT New False - - Chapter 3 - FILE - NOVEL + + Custom1 + ROOT + CUSTOM New - True - CHAPTER - 0 - 0 - 0 - 0 + False - - Scene 3.1 - FILE - NOVEL + + Custom2 + ROOT + CUSTOM New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 3.2 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 3.3 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 + False diff --git a/tests/reference/coreProject_5_nwProject.nwx b/tests/reference/coreProject_5_nwProject.nwx index 11d007aa..66488434 100644 --- a/tests/reference/coreProject_5_nwProject.nwx +++ b/tests/reference/coreProject_5_nwProject.nwx @@ -1,11 +1,9 @@ - + - Test Custom - Test Novel - Jane Doe - John Doh - 1 + New Project + + 2 1 0 @@ -40,7 +38,7 @@ Main - + Novel ROOT @@ -63,34 +61,13 @@ False - Locations + World ROOT WORLD New False - - Timeline - ROOT - TIMELINE - New - False - - - Objects - ROOT - OBJECT - New - False - - - Entity - ROOT - ENTITY - New - False - - + Title Page FILE NOVEL @@ -102,8 +79,27 @@ 0 0 - - Scene 1 + + New Chapter + FOLDER + NOVEL + New + False + + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + + New Scene FILE NOVEL New @@ -114,8 +110,8 @@ 0 0 - - Scene 2 + + Hello FILE NOVEL New @@ -126,49 +122,13 @@ 0 0 - - Scene 3 + + Jane FILE - NOVEL + CHARACTER New True - SCENE - 0 - 0 - 0 - 0 - - - Scene 4 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 5 - FILE - NOVEL - New - True - SCENE - 0 - 0 - 0 - 0 - - - Scene 6 - FILE - NOVEL - New - True - SCENE + NOTE 0 0 0 diff --git a/tests/test_core_project.py b/tests/test_core_project.py index 6cf20033..869603fe 100644 --- a/tests/test_core_project.py +++ b/tests/test_core_project.py @@ -7,15 +7,18 @@ import os from shutil import copyfile from zipfile import ZipFile +from lxml import etree -from tools import cmpFiles +from tools import cmpFiles, writeFile, readFile +from dummy import causeOSError from nw.core.project import NWProject from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles @pytest.mark.core -def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI): - """Test that a basic project can be created, opened and saved. +def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI): + """Create a new project from a project wizard dictionary. With + default setting, creating a Minimal project. """ projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx") @@ -29,7 +32,6 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI): # Try again with a proper path assert theProject.newProject({"projPath": fncDir}) - assert theProject.setProjectPath(fncDir) assert theProject.saveProject() assert theProject.closeProject() @@ -59,72 +61,7 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI): copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) -# END Test testCoreProject_NewOpenSave - -@pytest.mark.core -def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI): - """Check that new root folders can be added to the project. - """ - projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx") - - theProject = NWProject(dummyGUI) - theProject.projTree.setSeed(42) - - assert theProject.newProject({"projPath": fncDir}) - assert theProject.setProjectPath(fncDir) - assert theProject.saveProject() - assert theProject.closeProject() - assert theProject.openProject(projFile) - - assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None)) - assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None)) - assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None)) - assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), type(None)) - assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str) - assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str) - assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str) - assert isinstance(theProject.newRoot("Custom2", nwItemClass.CUSTOM), str) - - assert theProject.projChanged - assert theProject.saveProject() - assert theProject.closeProject() - - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) - assert not theProject.projChanged - -# END Test testCoreProject_NewRoot - -@pytest.mark.core -def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI): - """Check that new files can be added to the project. - """ - projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx") - - theProject = NWProject(dummyGUI) - theProject.projTree.setSeed(42) - - assert theProject.newProject({"projPath": fncDir}) - assert theProject.setProjectPath(fncDir) - assert theProject.saveProject() - assert theProject.closeProject() - assert theProject.openProject(projFile) - - assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str) - assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) - assert theProject.projChanged - assert theProject.saveProject() - assert theProject.closeProject() - - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) - assert not theProject.projChanged - -# END Test testCoreProject_NewFile +# END Test testCoreProject_NewMinimal @pytest.mark.core def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): @@ -132,8 +69,8 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): Custom type with chapters and scenes. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx") projData = { "projName": "Test Custom", @@ -173,8 +110,8 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI): Custom type without chapters, but with scenes. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx") projData = { "projName": "Test Custom", @@ -296,7 +233,418 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir): # END Test testCoreProject_NewSampleB @pytest.mark.core -def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI): +def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI): + """Check that new root folders can be added to the project. + """ + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx") + + theProject = NWProject(dummyGUI) + theProject.projTree.setSeed(42) + + assert theProject.newProject({"projPath": fncDir}) + assert theProject.setProjectPath(fncDir) + assert theProject.saveProject() + assert theProject.closeProject() + assert theProject.openProject(projFile) + + assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None)) + assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None)) + assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None)) + assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), type(None)) + assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str) + assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str) + assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str) + assert isinstance(theProject.newRoot("Custom2", nwItemClass.CUSTOM), str) + + assert theProject.projChanged + assert theProject.saveProject() + assert theProject.closeProject() + + copyfile(projFile, testFile) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) + assert not theProject.projChanged + +# END Test testCoreProject_NewRoot + +@pytest.mark.core +def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI): + """Check that new files can be added to the project. + """ + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx") + + theProject = NWProject(dummyGUI) + theProject.projTree.setSeed(42) + + assert theProject.newProject({"projPath": fncDir}) + assert theProject.setProjectPath(fncDir) + assert theProject.saveProject() + assert theProject.closeProject() + assert theProject.openProject(projFile) + + assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str) + assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) + assert theProject.projChanged + assert theProject.saveProject() + assert theProject.closeProject() + + copyfile(projFile, testFile) + assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) + assert not theProject.projChanged + +# END Test testCoreProject_NewFile + +@pytest.mark.core +def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI): + """Test opening a project. + """ + theProject = NWProject(dummyGUI) + + # Rename the project file to check handling + rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE) + wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj") + os.rename(rName, wName) + assert theProject.openProject(nwMinimal) is False + os.rename(wName, rName) + + # Fail on folder structure check + monkeypatch.setattr("os.mkdir", causeOSError) + assert theProject.openProject(nwMinimal) is False + monkeypatch.undo() + + # Fail on lock file + theProject.setProjectPath(nwMinimal) + assert theProject._writeLockFile() + assert theProject.openProject(nwMinimal) is False + + # Fail to read lockfile (which still opens the project) + monkeypatch.setattr("builtins.open", causeOSError) + assert theProject.openProject(nwMinimal) is True + monkeypatch.undo() + assert theProject.closeProject() + + # Force open with lockfile + theProject.setProjectPath(nwMinimal) + assert theProject._writeLockFile() + assert theProject.openProject(nwMinimal, 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, "dummy") + assert theProject.openProject(nwMinimal) is False + + # Also write a jun XML backup file + writeFile(bName, "dummy") + assert theProject.openProject(nwMinimal) is False + + # Wrong root item + writeFile(rName, "\n") + assert theProject.openProject(nwMinimal) is False + + # Wrong file version + writeFile(rName, ( + "\n" + "\n" + "\n" + )) + dummyGUI.askResponse = False + assert theProject.openProject(nwMinimal) is False + dummyGUI.undo() + + # Future file version + writeFile(rName, ( + "\n" + "\n" + "\n" + )) + assert theProject.openProject(nwMinimal) is False + + # Larger hex version + writeFile(rName, ( + "\n" + "\n" + "\n" + )) + dummyGUI.askResponse = False + assert theProject.openProject(nwMinimal) is False + dummyGUI.undo() + + # Test skipping XML entries + writeFile(rName, ( + "\n" + "\n" + "\n" + "\n" + "\n" + )) + assert theProject.openProject(nwMinimal) is True + assert theProject.closeProject() + + # Test deprecated XML entries + writeFile(rName, ( + "\n" + "\n" + "\n" + "\n" + "B\n" + "\n" + "\n" + "\n" + )) + assert theProject.openProject(nwMinimal) is True + assert theProject.autoReplace == {"A": "B"} + 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 + writeFile(os.path.join(nwMinimal, "junk"), "dummy") + os.mkdir(os.path.join(nwMinimal, "data_0")) + writeFile(os.path.join(nwMinimal, "data_0", "junk"), "dummy") + dummyGUI.clear() + assert theProject.openProject(nwMinimal) is True + assert "data_0" in dummyGUI.lastAlert + assert theProject.closeProject() + +# END Test testCoreProject_Open + +@pytest.mark.core +def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir): + """Test saving a project. + """ + theProject = NWProject(dummyGUI) + testFile = os.path.join(nwMinimal, "nwProject.nwx") + 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) + + # Fail on folder structure check + monkeypatch.setattr("os.path.isdir", lambda *args: False) + assert theProject.saveProject() is False + monkeypatch.undo() + + # Fail on open file + monkeypatch.setattr("builtins.open", causeOSError) + assert theProject.saveProject() is False + monkeypatch.undo() + + # Successful save + saveCount = theProject.saveCount + autoCount = theProject.autoCount + assert theProject.saveProject() is True + assert theProject.saveCount == saveCount + 1 + assert theProject.autoCount == autoCount + assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9]) + + # Successful autosave + saveCount = theProject.saveCount + autoCount = theProject.autoCount + assert theProject.saveProject(autoSave=True) is True + assert theProject.saveCount == saveCount + assert theProject.autoCount == autoCount + 1 + assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9]) + + # Close test project + assert theProject.closeProject() + +# END Test testCoreProject_Save + +@pytest.mark.core +def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI): + """Test lock file functions for the project folder. + """ + theProject = NWProject(dummyGUI) + + lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK) + + # No project + assert theProject._writeLockFile() is False + assert theProject._readLockFile() == ["ERROR"] + assert theProject._clearLockFile() is False + + theProject.projPath = fncDir + theProject.mainConf.hostName = "TestHost" + theProject.mainConf.osType = "TestOS" + theProject.mainConf.kernelVer = "1.0" + + # Block open + monkeypatch.setattr("builtins.open", causeOSError) + assert theProject._writeLockFile() is False + monkeypatch.undo() + + # Write lock file + monkeypatch.setattr("nw.core.project.time", lambda: 123.4) + assert theProject._writeLockFile() is True + monkeypatch.undo() + assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n" + + # Block open + monkeypatch.setattr("builtins.open", causeOSError) + assert theProject._readLockFile() == ["ERROR"] + monkeypatch.undo() + + # Read lock file + assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"] + + # Block unlink + monkeypatch.setattr("os.unlink", causeOSError) + assert os.path.isfile(lockFile) + assert theProject._clearLockFile() is False + assert os.path.isfile(lockFile) + monkeypatch.undo() + + # Clear file + assert os.path.isfile(lockFile) + assert theProject._clearLockFile() is True + assert not os.path.isfile(lockFile) + + # Read again, no file + assert theProject._readLockFile() == [] + + # Read an invalid lock file + writeFile(lockFile, "A\nB") + assert theProject._readLockFile() == ["ERROR"] + assert theProject._clearLockFile() is True + +# END Test testCoreProject_LockFile + +@pytest.mark.core +def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI): + """Test helper functions for the project folder. + """ + theProject = NWProject(dummyGUI) + + # No path + assert theProject.ensureFolderStructure() is False + + # Set the correct dir + theProject.projPath = fncDir + + # Block user's home folder + monkeypatch.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir) + assert theProject.ensureFolderStructure() is False + monkeypatch.undo() + + # Create a file to block meta folder + metaDir = os.path.join(fncDir, "meta") + writeFile(metaDir, "dummy") + assert theProject.ensureFolderStructure() is False + os.unlink(metaDir) + + # Create a file to block cache folder + cacheDir = os.path.join(fncDir, "cache") + writeFile(cacheDir, "dummy") + assert theProject.ensureFolderStructure() is False + os.unlink(cacheDir) + + # Create a file to block content folder + contentDir = os.path.join(fncDir, "content") + writeFile(contentDir, "dummy") + assert theProject.ensureFolderStructure() is False + os.unlink(contentDir) + + # Now, do it right + assert theProject.ensureFolderStructure() is True + assert os.path.isdir(metaDir) + assert os.path.isdir(cacheDir) + assert os.path.isdir(contentDir) + +# END Test testCoreProject_Helpers + +@pytest.mark.core +def testCoreProject_AccessItems(nwMinimal, dummyGUI): + """Test helper functions for the project folder. + """ + theProject = NWProject(dummyGUI) + theProject.openProject(nwMinimal) + + # 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 + ] + 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 + ] + assert theProject.projTree.handles() == oldOrder + assert theProject.setTreeOrder(newOrder) + assert theProject.projTree.handles() == newOrder + + # Add a non-existing item + theProject.projTree._treeOrder.append("01234567789abc") + + # Add an item with a non-existent parent + nHandle = theProject.newFile("Test File", nwItemClass.NOVEL, "a6d311a93600a") + theProject.projTree[nHandle].setParent("cba9876543210") + assert theProject.projTree[nHandle].itemParent == "cba9876543210" + + retOrder = [] + for tItem in theProject.getProjectItems(): + 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 + ] + assert theProject.projTree[nHandle].itemParent is None + +# END Test testCoreProject_AccessItems + +@pytest.mark.core +def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir): """Test other project class methods and functions. """ theProject = NWProject(dummyGUI) @@ -317,13 +665,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI): assert theProject.setProjectPath(projPath, newProject=True) # Make os.mkdir fail - def altMkdir(*args): - raise Exception("Oops!") - - monkeypatch.setattr("os.mkdir", altMkdir) + monkeypatch.setattr("os.mkdir", causeOSError) projPath = os.path.join(nwMinimal, "dummy2") assert not theProject.setProjectPath(projPath, newProject=True) + # Set back + assert theProject.setProjectPath(nwMinimal) + # Project Name assert theProject.setProjectName(" A Name ") assert theProject.projName == "A Name" @@ -337,6 +685,191 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI): assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.bookAuthors == ["Jane Doe", "John Doh"] + # Trash folder + # Should create on first call, and just returned on later calls + assert theProject.projTree["73475cb40a568"] is None + assert theProject.trashFolder() == "73475cb40a568" + assert theProject.trashFolder() == "73475cb40a568" + + # Project backup + assert theProject.doBackup is True + assert theProject.setProjBackup(False) + assert theProject.doBackup is False + + assert not theProject.setProjBackup(True) + theProject.mainConf.backupPath = tmpDir + assert theProject.setProjBackup(True) + + assert theProject.setProjectName("") + assert not theProject.setProjBackup(True) + assert theProject.setProjectName("A Name") + assert theProject.setProjBackup(True) + + # Spell check + theProject.projChanged = False + assert theProject.setSpellCheck(True) + assert not theProject.setSpellCheck(False) + assert theProject.projChanged + + # Spell language + theProject.projChanged = False + assert theProject.setSpellLang(None) + assert theProject.projLang is None + assert theProject.setSpellLang("None") + assert theProject.projLang is None + assert theProject.setSpellLang("en_GB") + assert theProject.projLang == "en_GB" + assert theProject.projChanged + + # Automatic outline update + theProject.projChanged = False + assert theProject.setAutoOutline(True) + assert not theProject.setAutoOutline(False) + assert theProject.projChanged + + # Last edited + theProject.projChanged = False + assert theProject.setLastEdited("0123456789abc") + assert theProject.lastEdited == "0123456789abc" + assert theProject.projChanged + + # Last viewed + theProject.projChanged = False + assert theProject.setLastViewed("0123456789abc") + assert theProject.lastViewed == "0123456789abc" + assert theProject.projChanged + + # Autoreplace + theProject.projChanged = False + assert theProject.setAutoReplace({"A": "B", "C": "D"}) + assert theProject.autoReplace == {"A": "B", "C": "D"} + assert theProject.projChanged + + # Change project tree order + oldOrder = [ + "a508bb932959c", "a35baf2e93843", "a6d311a93600a", + "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265", + "afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568", + ] + newOrder = [ + "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265", + "a508bb932959c", "a35baf2e93843", "a6d311a93600a", + "afb3043c7b2b3", "9d5247ab588e0", + ] + assert theProject.projTree.handles() == oldOrder + assert theProject.setTreeOrder(newOrder) + assert theProject.projTree.handles() == newOrder + assert theProject.setTreeOrder(oldOrder) + assert theProject.projTree.handles() == oldOrder + + # Change status + theProject.projTree["a35baf2e93843"].setStatus("Finished") + theProject.projTree["a6d311a93600a"].setStatus("Draft") + theProject.projTree["f5ab3e30151e1"].setStatus("Note") + theProject.projTree["8c659a11cd429"].setStatus("Finished") + newList = [ + ("New", 1, 1, 1, "New"), + ("Draft", 2, 2, 2, "Note"), # These are swapped + ("Note", 3, 3, 3, "Draft"), # These are swapped + ("Edited", 4, 4, 4, "Finished"), # Renamed + ("Finished", 5, 5, 5, None), # New, with reused name + ] + assert theProject.setStatusColours(newList) + assert theProject.statusItems._theLabels == [ + "New", "Draft", "Note", "Edited", "Finished" + ] + assert theProject.statusItems._theColours == [ + (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5) + ] + assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed + assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped + assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped + assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed + + # Change importance + fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3") + theProject.projTree[fHandle].setStatus("Main") + newList = [ + ("New", 1, 1, 1, "New"), + ("Minor", 2, 2, 2, "Minor"), + ("Major", 3, 3, 3, "Major"), + ("Min", 4, 4, 4, "Main"), + ("Max", 5, 5, 5, None), + ] + assert theProject.setImportColours(newList) + assert theProject.importItems._theLabels == [ + "New", "Minor", "Major", "Min", "Max" + ] + assert theProject.importItems._theColours == [ + (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5) + ] + assert theProject.projTree[fHandle].itemStatus == "Min" + + # Check status counts + assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0] + assert theProject.importItems._theCounts == [0, 0, 0, 0, 0] + theProject.countStatus() + assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0] + assert theProject.importItems._theCounts == [3, 0, 0, 1, 0] + + # Check word counts + theProject.currWCount = 200 + theProject.lastWCount = 100 + assert theProject.getSessionWordCount() == 100 + + # Session stats + monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False) + assert not theProject._appendSessionStats() + monkeypatch.undo() + + # Block open + monkeypatch.setattr("builtins.open", causeOSError) + assert not theProject._appendSessionStats() + monkeypatch.undo() + + # Write entry + assert theProject.projMeta == os.path.join(nwMinimal, "meta") + statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS) + + theProject.projOpened = 1600002000 + theProject.novelWCount = 200 + theProject.notesWCount = 100 + + monkeypatch.setattr("nw.core.project.time", lambda: 1600005600) + assert theProject._appendSessionStats() + monkeypatch.undo() + + assert readFile(statsFile) == ( + "# Offset 100\n" + "# Start Time End Time Novel Notes\n" + "2020-09-13 13:00:00 2020-09-13 14:00:00 200 100\n" + ) + + # Pack XML Value + xElem = etree.Element("element") + theProject._packProjectValue(xElem, "A", "B", allowNone=False) + assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == ( + b"B" + ) + + xElem = etree.Element("element") + theProject._packProjectValue(xElem, "A", "", allowNone=False) + assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == ( + b"" + ) + + # Pack XML Key/Value + xElem = etree.Element("element") + theProject._packProjectKeyValue(xElem, "item", {"A": "B", "C": "D"}) + assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == ( + b"" + b"" + b"B" + b"D" + b"" + b"" + ) + # END Test testCoreProject_Methods @pytest.mark.core @@ -347,6 +880,7 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum): the meta line at the top of the document file. """ theProject = NWProject(dummyGUI) + assert theProject.openProject(nwLipsum) assert theProject.projTree["636b6aa9b697b"] is None assert theProject.closeProject() @@ -408,6 +942,10 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum): assert theProject.saveProject(nwLipsum) assert theProject.closeProject() + # Finally, check that the orphaned files function returns + # if no project is open and no path is set + assert not theProject._scanProjectFolder() + # END Test testCoreProject_OrphanedFiles @pytest.mark.core @@ -418,7 +956,6 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj): contained in a single 'content' folder. """ theProject = NWProject(dummyGUI) - theProject.mainConf.showGUI = False # Create dummy files for known legacy files deleteFiles = [ @@ -451,8 +988,7 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj): # Create dummy files os.mkdir(os.path.join(nwOldProj, "cache")) for aFile in deleteFiles: - with open(aFile, mode="w+", encoding="utf8") as outFile: - outFile.write("Hi") + writeFile(aFile, "Hi") for aFile in deleteFiles: assert os.path.isfile(aFile) @@ -503,7 +1039,112 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj): # END Test testCoreProject_OldFormat @pytest.mark.core -def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir): +def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir): + """Test the functins that handle legacy data folders and structure + with additional tests of failure handling. + """ + theProject = NWProject(dummyGUI) + theProject.setProjectPath(fncDir) + + # assert theProject.newProject({"projPath": fncDir}) + # assert theProject.saveProject() + # assert theProject.closeProject() + + # Check behaviour of deprecated files function on OSError + tstFile = os.path.join(fncDir, "ToC.json") + writeFile(tstFile, "dummy") + assert os.path.isfile(tstFile) + + monkeypatch.setattr("os.unlink", causeOSError) + assert not theProject._deprecatedFiles() + monkeypatch.undo() + + assert theProject._deprecatedFiles() + assert not os.path.isfile(tstFile) + + # Check processing non-folders + tstFile = os.path.join(fncDir, "data_0") + writeFile(tstFile, "dummy") + assert os.path.isfile(tstFile) + + errList = [] + errList = theProject._legacyDataFolder(tstFile, errList) + assert len(errList) > 0 + + # Move folder in data folder, shouldn't be there + tstData = os.path.join(fncDir, "data_1") + errItem = os.path.join(fncDir, "data_1", "stuff") + os.mkdir(tstData) + os.mkdir(errItem) + assert os.path.isdir(tstData) + assert os.path.isdir(errItem) + + # This causes a failure to create the 'junk' folder + monkeypatch.setattr("os.mkdir", causeOSError) + errList = [] + errList = theProject._legacyDataFolder(tstData, errList) + assert len(errList) > 0 + monkeypatch.undo() + + # This causes a failure to move 'stuff' to 'junk' + monkeypatch.setattr("os.rename", causeOSError) + errList = [] + errList = theProject._legacyDataFolder(tstData, errList) + assert len(errList) > 0 + monkeypatch.undo() + + # This should be successful + errList = [] + errList = theProject._legacyDataFolder(tstData, errList) + assert len(errList) == 0 + assert os.path.isdir(os.path.join(fncDir, "junk", "stuff")) + + # Check renaming/deleting of old document files + tstData = os.path.join(fncDir, "data_2") + tstDoc1m = os.path.join(tstData, "000000000001_main.nwd") + tstDoc1b = os.path.join(tstData, "000000000001_main.bak") + tstDoc2m = os.path.join(tstData, "000000000002_main.nwd") + tstDoc2b = os.path.join(tstData, "000000000002_main.bak") + tstDoc3m = os.path.join(tstData, "tooshort003_main.nwd") + tstDoc3b = os.path.join(tstData, "tooshort003_main.bak") + + os.mkdir(tstData) + writeFile(tstDoc1m, "dummy") + writeFile(tstDoc1b, "dummy") + writeFile(tstDoc2m, "dummy") + writeFile(tstDoc2b, "dummy") + writeFile(tstDoc3m, "dummy") + writeFile(tstDoc3b, "dummy") + + # Make the above fail + monkeypatch.setattr("os.rename", causeOSError) + monkeypatch.setattr("os.unlink", causeOSError) + errList = [] + errList = theProject._legacyDataFolder(tstData, errList) + assert len(errList) > 0 + assert os.path.isfile(tstDoc1m) + assert os.path.isfile(tstDoc1b) + assert os.path.isfile(tstDoc2m) + assert os.path.isfile(tstDoc2b) + assert os.path.isfile(tstDoc3m) + assert os.path.isfile(tstDoc3b) + monkeypatch.undo() + + # And succeed ... + errList = [] + errList = theProject._legacyDataFolder(tstData, errList) + assert len(errList) == 0 + + assert not os.path.isdir(tstData) + assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd")) + assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd")) + assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.nwd")) + assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.bak")) + +# END Test testCoreProject_LegacyData + +@pytest.mark.core +def testCoreProject_Backup(monkeypatch, dummyGUI, nwMinimal, 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 @@ -513,6 +1154,12 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir): assert theProject.openProject(nwMinimal) # Test faulty settings + + # No project + dummyGUI.hasProject = False + assert not theProject.zipIt(doNotify=False) + dummyGUI.hasProject = True + # Invalid path theProject.mainConf.backupPath = None assert not theProject.zipIt(doNotify=False) @@ -531,9 +1178,21 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir): theProject.mainConf.backupPath = nwMinimal assert not theProject.zipIt(doNotify=False) - # Test correct settings + # Set a valid folder theProject.mainConf.backupPath = tmpDir - assert theProject.zipIt(doNotify=False) + + # Can't make folder + monkeypatch.setattr("os.mkdir", causeOSError) + assert not theProject.zipIt(doNotify=False) + monkeypatch.undo() + + # Can't write archive + monkeypatch.setattr("shutil.make_archive", causeOSError) + assert not theProject.zipIt(doNotify=False) + monkeypatch.undo() + + # Test correct settings + assert theProject.zipIt(doNotify=True) theFiles = os.listdir(os.path.join(tmpDir, "Test Minimal")) assert len(theFiles) == 1 @@ -548,7 +1207,8 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir): # Check that the main project file was restored assert cmpFiles( - os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx") + os.path.join(nwMinimal, "nwProject.nwx"), + os.path.join(tmpDir, "extract", "nwProject.nwx") ) # END Test testCoreProject_Backup diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py index f417fd6a..29c1ca95 100644 --- a/tests/test_core_tree.py +++ b/tests/test_core_tree.py @@ -10,7 +10,7 @@ from lxml import etree from nw.core.project import NWProject, NWItem, NWTree from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def dummyItems(dummyGUI): """Create a list of dummy items. """ @@ -353,7 +353,7 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems): b"True" b"" b"Chapter OneFILENOVELNone" - b"TrueUNNUMBERED300" + b"TrueCHAPTER300" b"5020" b"" b"Scene OneFILENOVELNone" @@ -425,7 +425,7 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir): "\n" "File Name Class Layout Document Label\n" "-------------------------------------------------------------\n" - f"{pathA} NOVEL UNNUMBERED Chapter One\n" + f"{pathA} NOVEL CHAPTER Chapter One\n" f"{pathB} NOVEL SCENE Scene One\n" f"{pathC} CHARACTER NOTE Jane Doe\n" ) From d274c418d141d51e1cbb45248c39963ce79dca77 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 5 Dec 2020 21:08:42 +0100 Subject: [PATCH 20/22] Remove nwFuncTemp fixture --- tests/conftest.py | 14 ------------- tests/test_dialogs.py | 40 ++++++++++++++++++------------------- tests/test_error.py | 4 ++-- tests/test_gui.py | 46 +++++++++++++++++++++---------------------- 4 files changed, 45 insertions(+), 59 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1f7b88ae..5195b841 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -198,17 +198,3 @@ def nwTempBuild(tmpDir): if not os.path.isdir(buildDir): os.mkdir(buildDir) return buildDir - -@pytest.fixture(scope="function") -def nwFuncTemp(tmpDir): - """A temporary folder for a single test function. - """ - funcDir = os.path.join(tmpDir, "ftemp") - if os.path.isdir(funcDir): - shutil.rmtree(funcDir) - if not os.path.isdir(funcDir): - os.mkdir(funcDir) - yield funcDir - if os.path.isdir(funcDir): - shutil.rmtree(funcDir) - return diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index b8e3b276..96f64da4 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -30,7 +30,7 @@ typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): +def testProjectSettings(qtbot, monkeypatch, yesToAll, fncDir, nwTempGUI, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -43,8 +43,8 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, ref # Create new project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwFuncTemp}) - nwGUI.mainConf.backupPath = nwFuncTemp + assert nwGUI.newProject({"projPath": fncDir}) + nwGUI.mainConf.backupPath = fncDir # Get the dialog object monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None) @@ -135,7 +135,7 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, ref qtbot.wait(stepDelay) # Check the files - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "2_nwProject.nwx") refFile = os.path.join(refDir, "gui", "2_nwProject.nwx") copyfile(projFile, testFile) @@ -145,7 +145,7 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, ref nwGUI.closeMain() @pytest.mark.gui -def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir, tmpDir): +def testItemEditor(qtbot, yesToAll, monkeypatch, fncDir, nwTempGUI, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -154,7 +154,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir, # Create new, save, open project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwFuncTemp}) + assert nwGUI.newProject({"projPath": fncDir}) assert nwGUI.openDocument("0e17daca5f3e1") assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True) @@ -208,7 +208,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir, qtbot.wait(stepDelay) # Check the files - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "3_nwProject.nwx") refFile = os.path.join(refDir, "gui", "3_nwProject.nwx") copyfile(projFile, testFile) @@ -218,7 +218,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir, nwGUI.closeMain() @pytest.mark.gui -def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): +def testWritingStatsExport(qtbot, monkeypatch, yesToAll, fncDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -227,13 +227,13 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): # Create new, save, close project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwFuncTemp}) + assert nwGUI.newProject({"projPath": fncDir}) qtbot.wait(200) assert nwGUI.saveProject() assert nwGUI.closeProject() qtbot.wait(stepDelay) - sessFile = os.path.join(nwFuncTemp, "meta", nwFiles.SESS_STATS) + sessFile = os.path.join(fncDir, "meta", nwFiles.SESS_STATS) with open(sessFile, mode="w+", encoding="utf-8") as outFile: outFile.write( "# Start Time End Time Novel Notes\n" @@ -244,10 +244,10 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): ) # Open again, and check the stats - assert nwGUI.openProject(nwFuncTemp) + assert nwGUI.openProject(fncDir) qtbot.wait(stepDelay) - nwGUI.mainConf.lastPath = nwFuncTemp + nwGUI.mainConf.lastPath = fncDir nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000) @@ -264,7 +264,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(100) - jsonStats = os.path.join(nwFuncTemp, "sessionStats.json") + jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) @@ -282,7 +282,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = os.path.join(nwFuncTemp, "sessionStats.json") + jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) @@ -299,7 +299,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = os.path.join(nwFuncTemp, "sessionStats.json") + jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) @@ -316,7 +316,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = os.path.join(nwFuncTemp, "sessionStats.json") + jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) @@ -329,7 +329,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = os.path.join(nwFuncTemp, "sessionStats.json") + jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) @@ -341,7 +341,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): assert sessLog._saveData(sessLog.FMT_JSON) qtbot.wait(stepDelay) - jsonStats = os.path.join(nwFuncTemp, "sessionStats.json") + jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) @@ -357,8 +357,8 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir): nwGUI.closeMain() @pytest.mark.gui -def testAboutBox(qtbot, monkeypatch, nwFuncTemp, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]) +def testAboutBox(qtbot, monkeypatch, fncDir, tmpDir): + nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) diff --git a/tests/test_error.py b/tests/test_error.py index 7ad07269..192fb6cb 100644 --- a/tests/test_error.py +++ b/tests/test_error.py @@ -10,9 +10,9 @@ from PyQt5.QtWidgets import qApp from nw.error import NWErrorMessage, exceptionHandler @pytest.mark.error -def testErrorDialog(qtbot, nwFuncTemp, tmpDir): +def testErrorDialog(qtbot, fncDir, tmpDir): qApp.closeAllWindows() - nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]) + nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.waitForWindowShown(nwGUI) diff --git a/tests/test_gui.py b/tests/test_gui.py index a6438d0a..a10c0582 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -27,11 +27,11 @@ typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): +def testLaunch(qtbot, monkeypatch, fncDir, tmpDir): # Defaults nwGUI = nw.main( - ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir, "--style=Fusion"] + ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir, "--style=Fusion"] ) assert nw.logger.getEffectiveLevel() == logging.WARNING nwGUI.closeMain() @@ -39,21 +39,21 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): # Log Levels nwGUI = nw.main( - ["--testmode", "--info", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.INFO nwGUI.closeMain() nwGUI.close() nwGUI = nw.main( - ["--testmode", "--debug", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--debug", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.DEBUG nwGUI.closeMain() nwGUI.close() nwGUI = nw.main( - ["--testmode", "--verbose", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--verbose", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == 5 nwGUI.closeMain() @@ -62,7 +62,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): # Help and Version with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--help", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--help", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -70,7 +70,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--version", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--version", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -79,7 +79,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): # Invalid options with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--invalid", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--invalid", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -92,7 +92,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000) with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir] + ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir] ) nwGUI.closeMain() nwGUI.close() @@ -103,7 +103,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir): monkeypatch.undo() @pytest.mark.gui -def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): +def testDocEditor(qtbot, yesToAll, fncDir, nwTempGUI, refDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) @@ -113,7 +113,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): # Create new, save, close project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwFuncTemp}) + assert nwGUI.newProject({"projPath": fncDir}) assert nwGUI.saveProject() assert nwGUI.closeProject() @@ -130,7 +130,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): assert not nwGUI.theProject.spellCheck # Check the files - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "0_nwProject.nwx") refFile = os.path.join(refDir, "gui", "0_nwProject.nwx") copyfile(projFile, testFile) @@ -140,7 +140,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): # qtbot.stopForInteraction() # Re-open project - assert nwGUI.openProject(nwFuncTemp) + assert nwGUI.openProject(fncDir) qtbot.wait(stepDelay) # Check that we loaded the data @@ -148,8 +148,8 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): assert len(nwGUI.theProject.projTree._treeOrder) == 8 assert len(nwGUI.theProject.projTree._treeRoots) == 4 assert nwGUI.theProject.projTree.trashRoot() is None - assert nwGUI.theProject.projPath == nwFuncTemp - assert nwGUI.theProject.projMeta == os.path.join(nwFuncTemp, "meta") + assert nwGUI.theProject.projPath == fncDir + assert nwGUI.theProject.projMeta == os.path.join(fncDir, "meta") assert nwGUI.theProject.projFile == "nwProject.nwx" assert nwGUI.theProject.projName == "New Project" assert nwGUI.theProject.bookTitle == "" @@ -379,31 +379,31 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir): assert nwGUI.saveProject() # Check the files - projFile = os.path.join(nwFuncTemp, "nwProject.nwx") + projFile = os.path.join(fncDir, "nwProject.nwx") testFile = os.path.join(nwTempGUI, "1_nwProject.nwx") refFile = os.path.join(refDir, "gui", "1_nwProject.nwx") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile, [2, 6, 7, 8]) - projFile = os.path.join(nwFuncTemp, "content", "031b4af5197ec.nwd") + projFile = os.path.join(fncDir, "content", "031b4af5197ec.nwd") testFile = os.path.join(nwTempGUI, "1_031b4af5197ec.nwd") refFile = os.path.join(refDir, "gui", "1_031b4af5197ec.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) - projFile = os.path.join(nwFuncTemp, "content", "1a6562590ef19.nwd") + projFile = os.path.join(fncDir, "content", "1a6562590ef19.nwd") testFile = os.path.join(nwTempGUI, "1_1a6562590ef19.nwd") refFile = os.path.join(refDir, "gui", "1_1a6562590ef19.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) - projFile = os.path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd") + projFile = os.path.join(fncDir, "content", "0e17daca5f3e1.nwd") testFile = os.path.join(nwTempGUI, "1_0e17daca5f3e1.nwd") refFile = os.path.join(refDir, "gui", "1_0e17daca5f3e1.nwd") copyfile(projFile, testFile) assert cmpFiles(testFile, refFile) - projFile = os.path.join(nwFuncTemp, "content", "41cfc0d1f2d12.nwd") + projFile = os.path.join(fncDir, "content", "41cfc0d1f2d12.nwd") testFile = os.path.join(nwTempGUI, "1_41cfc0d1f2d12.nwd") refFile = os.path.join(refDir, "gui", "1_41cfc0d1f2d12.nwd") copyfile(projFile, testFile) @@ -1013,7 +1013,7 @@ def testContextMenu(qtbot, yesToAll, nwLipsum, tmpDir): nwGUI.close() @pytest.mark.gui -def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir): +def testInsertMenu(qtbot, monkeypatch, fncDir, tmpDir): nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) nwGUI.show() @@ -1021,7 +1021,7 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir): qtbot.wait(stepDelay) nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject({"projPath": nwFuncTemp}) + assert nwGUI.newProject({"projPath": fncDir}) assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None @@ -1205,7 +1205,7 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir): assert len(theBits) == 3 assert theBits[0] == "File details for the currently open file" assert theBits[1] == "Handle: 0e17daca5f3e1" - assert theBits[2] == "Location: %s" % os.path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd") + assert theBits[2] == "Location: %s" % os.path.join(fncDir, "content", "0e17daca5f3e1.nwd") # qtbot.stopForInteraction() nwGUI.closeMain() From 51cb9814d2f035622a983c22a57f03dd7874d53d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 5 Dec 2020 21:10:34 +0100 Subject: [PATCH 21/22] Rename GUI test files --- tests/{test_dialogs.py => test_gui_dialogs.py} | 0 tests/{test_gui.py => test_gui_main.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{test_dialogs.py => test_gui_dialogs.py} (100%) rename tests/{test_gui.py => test_gui_main.py} (100%) diff --git a/tests/test_dialogs.py b/tests/test_gui_dialogs.py similarity index 100% rename from tests/test_dialogs.py rename to tests/test_gui_dialogs.py diff --git a/tests/test_gui.py b/tests/test_gui_main.py similarity index 100% rename from tests/test_gui.py rename to tests/test_gui_main.py From bd9ed0d5d3c003b13692d87871fa513eff0cd6e6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 5 Dec 2020 21:24:19 +0100 Subject: [PATCH 22/22] Fix testCoreProject_Methods test on Windows --- tests/conftest.py | 3 --- tests/test_core_project.py | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5195b841..0d66d0ea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,15 +6,12 @@ import sys import pytest import shutil import os -import time from dummy import DummyMain from PyQt5.QtWidgets import QMessageBox sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) -os.environ["TZ"] = "UTC" -time.tzset() from nw.config import Config # noqa: E402 diff --git a/tests/test_core_project.py b/tests/test_core_project.py index 869603fe..fe847983 100644 --- a/tests/test_core_project.py +++ b/tests/test_core_project.py @@ -14,6 +14,7 @@ from dummy import causeOSError from nw.core.project import NWProject from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles +from nw.common import formatTimeStamp @pytest.mark.core def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI): @@ -842,8 +843,8 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir): assert readFile(statsFile) == ( "# Offset 100\n" "# Start Time End Time Novel Notes\n" - "2020-09-13 13:00:00 2020-09-13 14:00:00 200 100\n" - ) + "%s %s 200 100\n" + ) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600)) # Pack XML Value xElem = etree.Element("element")