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")