Fix tests for core classes

This commit is contained in:
Veronica Berglyd Olsen
2022-11-08 19:48:06 +01:00
parent b024c0996d
commit ee79d3d6d6
9 changed files with 163 additions and 124 deletions
+2 -2
View File
@@ -138,7 +138,7 @@ class NWDocument:
contentPath = self.theProject.storage.contentPath contentPath = self.theProject.storage.contentPath
if not isinstance(contentPath, Path): if not isinstance(contentPath, Path):
logger.error("No content path set") logger.error("No content path set")
return None return False
docFile = self._docHandle+".nwd" docFile = self._docHandle+".nwd"
logger.debug("Saving document: %s", docFile) logger.debug("Saving document: %s", docFile)
@@ -195,7 +195,7 @@ class NWDocument:
contentPath = self.theProject.storage.contentPath contentPath = self.theProject.storage.contentPath
if not isinstance(contentPath, Path): if not isinstance(contentPath, Path):
logger.error("No content path set") logger.error("No content path set")
return None return False
docPath = contentPath / f"{self._docHandle}.nwd" docPath = contentPath / f"{self._docHandle}.nwd"
docTemp = docPath.with_suffix(".tmp") docTemp = docPath.with_suffix(".tmp")
-2
View File
@@ -820,8 +820,6 @@ class ItemIndex:
elif tItem.itemRoot == rootHandle: elif tItem.itemRoot == rootHandle:
for sTitle in self._items[tHandle].headings(): for sTitle in self._items[tHandle].headings():
yield tHandle, sTitle, self._items[tHandle][sTitle] yield tHandle, sTitle, self._items[tHandle][sTitle]
else:
continue
return return
+12 -18
View File
@@ -25,7 +25,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
import os
import json import json
import logging import logging
import novelwriter import novelwriter
@@ -82,8 +81,7 @@ class NWProject(QObject):
self.lockedBy = None # Data on which computer has the project open self.lockedBy = None # Data on which computer has the project open
# Class Settings # Class Settings
self.projDict = None # The spell check dictionary self.projFiles = [] # A list of all files in the content folder on load
self.projFiles = [] # A list of all files in the content folder on load
# Internal Mapping # Internal Mapping
self.tr = partial(QCoreApplication.translate, "NWProject") self.tr = partial(QCoreApplication.translate, "NWProject")
@@ -248,7 +246,6 @@ class NWProject(QObject):
self._data = NWProjectData(self) self._data = NWProjectData(self)
# Project Settings # Project Settings
self.projDict = None
self.projFiles = [] self.projFiles = []
return return
@@ -265,8 +262,6 @@ class NWProject(QObject):
logger.info("Opening project: %s", projPath) logger.info("Opening project: %s", projPath)
self.projDict = str(self._storage.getMetaFile(nwFiles.PROJ_DICT))
# Project Lock # Project Lock
# ============ # ============
@@ -367,11 +362,12 @@ class NWProject(QObject):
# Check the project tree consistency # Check the project tree consistency
for tItem in self._tree: for tItem in self._tree:
tHandle = tItem.itemHandle if tItem:
logger.debug("Checking item '%s'", tHandle) tHandle = tItem.itemHandle
if not self._tree.updateItemData(tHandle): logger.debug("Checking item '%s'", tHandle)
logger.error("There was a problem item '%s', and it has been removed", tHandle) if not self._tree.updateItemData(tHandle):
del self._tree[tHandle] # The file will be re-added as orphaned logger.error("There was a problem the item, and it has been removed")
del self._tree[tHandle] # The file will be re-added as orphaned
self._scanProjectFolder() self._scanProjectFolder()
self._index.loadIndex() self._index.loadIndex()
@@ -694,20 +690,18 @@ class NWProject(QObject):
def _loadProjectLocalisation(self): def _loadProjectLocalisation(self):
"""Load the language data for the current project language. """Load the language data for the current project language.
""" """
if self._data.language is None: if self._data.language is None or self.mainConf.nwLangPath is None:
self._langData = {} self._langData = {}
return False return False
langFile = os.path.join( langFile = Path(self.mainConf.nwLangPath) / f"project_{self._data.language}.json"
self.mainConf.nwLangPath, "project_%s.json" % self._data.language if not langFile.is_file():
) langFile = Path(self.mainConf.nwLangPath) / "project_en_GB.json"
if not os.path.isfile(langFile):
langFile = os.path.join(self.mainConf.nwLangPath, "project_en_GB.json")
try: try:
with open(langFile, mode="r", encoding="utf-8") as inFile: with open(langFile, mode="r", encoding="utf-8") as inFile:
self._langData = json.load(inFile) self._langData = json.load(inFile)
logger.debug("Loaded project language file: %s", os.path.basename(langFile)) logger.debug("Loaded project language file: %s", langFile.name)
except Exception: except Exception:
logger.error("Failed to project language file") logger.error("Failed to project language file")
+3 -3
View File
@@ -23,10 +23,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import logging import logging
from collections import namedtuple from collections import namedtuple
from pathlib import Path
from novelwriter.error import logException from novelwriter.error import logException
@@ -173,10 +173,10 @@ class NWSpellEnchant:
self._projDict = set() self._projDict = set()
self._projectDict = projectDict self._projectDict = projectDict
if projectDict is None: if not isinstance(projectDict, Path):
return False return False
if not os.path.isfile(projectDict): if not projectDict.exists():
return False return False
try: try:
+3 -2
View File
@@ -53,7 +53,7 @@ from PyQt5.QtWidgets import (
from novelwriter.core import NWSpellEnchant, countWords from novelwriter.core import NWSpellEnchant, countWords
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
from novelwriter.common import transferCase from novelwriter.common import transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode from novelwriter.constants import nwConst, nwFiles, nwKeyWords, nwUnicode
from novelwriter.gui.dochighlight import GuiDocHighlighter from novelwriter.gui.dochighlight import GuiDocHighlighter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -689,7 +689,8 @@ class GuiDocEditor(QTextEdit):
else: else:
theLang = self.theProject.data.spellLang theLang = self.theProject.data.spellLang
self.spEnchant.setLanguage(theLang, self.theProject.projDict) projDict = self.theProject.storage.getMetaFile(nwFiles.PROJ_DICT)
self.spEnchant.setLanguage(theLang, projDict)
_, theProvider = self.spEnchant.describeDict() _, theProvider = self.spEnchant.describeDict()
self.spellDictionaryChanged.emit(str(theLang), str(theProvider)) self.spellDictionaryChanged.emit(str(theLang), str(theProvider))
+30 -13
View File
@@ -19,7 +19,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import pytest import pytest
from mock import causeOSError from mock import causeOSError
@@ -31,12 +30,12 @@ from novelwriter.core.document import NWDocument
@pytest.mark.core @pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd): def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test loading and saving a document with the NWDocument class. """Test loading and saving a document with the NWDocument class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Read Document # Read Document
# ============= # =============
@@ -51,6 +50,12 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
assert theDoc.readDocument() is None assert theDoc.readDocument() is None
assert theDoc._currHash is None assert theDoc._currHash is None
# No content path
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.contentPath", property(lambda *a: None))
theDoc = NWDocument(theProject, C.hSceneDoc)
assert theDoc.readDocument() is None
# Cause open() to fail while loading # Cause open() to fail while loading
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("builtins.open", causeOSError)
@@ -72,17 +77,23 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
# Write Document # Write Document
# ============== # ==============
# Set handle and save again # No content path
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.contentPath", property(lambda *a: None))
theDoc = NWDocument(theProject, xHandle)
assert theDoc.writeDocument("") is False
# Set handle and save
theText = "### Test File\n\nText ...\n\n" theText = "### Test File\n\nText ...\n\n"
theDoc = NWDocument(theProject, xHandle) theDoc = NWDocument(theProject, xHandle)
assert theDoc.readDocument(xHandle) == "" assert theDoc.readDocument(xHandle) == ""
assert theDoc.writeDocument(theText) is True assert theDoc.writeDocument(theText) is True
# Save again to ensure temp file and previous file is handled # Save again to ensure temp file and previous file is handled
assert theDoc.writeDocument(theText) assert theDoc.writeDocument(theText) is True
# Check file content # Check file content
docPath = os.path.join(fncDir, "content", xHandle+".nwd") docPath = fncPath / "content" / f"{xHandle}.nwd"
assert readFile(docPath) == ( assert readFile(docPath) == (
"%%~name: New File\n" "%%~name: New File\n"
f"%%~path: {C.hNovelRoot}/{xHandle}\n" f"%%~path: {C.hNovelRoot}/{xHandle}\n"
@@ -128,10 +139,16 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
# Delete Document # Delete Document
# =============== # ===============
# Delete the last document # Delete a non-existing document
theDoc = NWDocument(theProject, "stuff") theDoc = NWDocument(theProject, "stuff")
assert theDoc.deleteDocument() is False assert theDoc.deleteDocument() is False
assert os.path.isfile(docPath) assert docPath.exists()
# No content path
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.contentPath", property(lambda *a: None))
theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is False
# Cause the delete to fail # Cause the delete to fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -143,26 +160,26 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
# Make the delete pass # Make the delete pass
theDoc = NWDocument(theProject, xHandle) theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is True assert theDoc.deleteDocument() is True
assert not os.path.isfile(docPath) assert not docPath.exists()
# END Test testCoreDocument_Load # END Test testCoreDocument_Load
@pytest.mark.core @pytest.mark.core
def testCoreDocument_Methods(mockGUI, fncDir, mockRnd): def testCoreDocument_Methods(mockGUI, fncPath, mockRnd):
"""Test other methods of the NWDocument class. """Test other methods of the NWDocument class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
theDoc = NWDocument(theProject, C.hSceneDoc) theDoc = NWDocument(theProject, C.hSceneDoc)
docPath = os.path.join(fncDir, "content", C.hSceneDoc+".nwd") docPath = fncPath / "content" / f"{C.hSceneDoc}.nwd"
assert theDoc.readDocument() == "### New Scene\n\n" assert theDoc.readDocument() == "### New Scene\n\n"
# Check location # Check location
assert theDoc.getFileLocation() == docPath assert theDoc.getFileLocation() == str(docPath)
# Check the item # Check the item
assert theDoc.getCurrentItem() is not None assert theDoc.getCurrentItem() is not None
+15 -5
View File
@@ -19,11 +19,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import json import json
import pytest import pytest
from shutil import copyfile from shutil import copyfile
from pathlib import Path
from mock import causeException from mock import causeException
from tools import C, buildTestProject, cmpFiles, writeFile from tools import C, buildTestProject, cmpFiles, writeFile
@@ -35,13 +35,13 @@ from novelwriter.core.project import NWProject
@pytest.mark.core @pytest.mark.core
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir): def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, tstPaths):
"""Test core functionality of scaning, saving, loading and checking """Test core functionality of scaning, saving, loading and checking
the index cache file. the index cache file.
""" """
projFile = os.path.join(nwLipsum, "meta", nwFiles.INDEX_FILE) projFile = Path(nwLipsum) / "meta" / nwFiles.INDEX_FILE
testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json") testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json"
compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json"
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
@@ -62,6 +62,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
assert theIndex.reIndexHandle(None) is False assert theIndex.reIndexHandle(None) is False
# No folder for saving
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.getMetaFile", lambda *a: None)
assert theIndex.saveIndex() is False
# Make the save fail # Make the save fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeException) mp.setattr("builtins.open", causeException)
@@ -86,6 +91,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
assert theIndex._tagsIndex._tags == {} assert theIndex._tagsIndex._tags == {}
assert theIndex._itemIndex._items == {} assert theIndex._itemIndex._items == {}
# No folder for sloading
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.getMetaFile", lambda *a: None)
assert theIndex.loadIndex() is False
# Make the load fail # Make the load fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(json, "load", causeException) mp.setattr(json, "load", causeException)
+93 -73
View File
@@ -19,8 +19,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import shutil
import pytest import pytest
from time import time from time import time
@@ -42,16 +40,16 @@ from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLR
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd): def testCoreProject_NewRoot(fncPath, tstPaths, mockGUI, mockRnd):
"""Check that new root folders can be added to the project. """Check that new root folders can be added to the project.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = fncPath / "nwProject.nwx"
testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx") testFile = tstPaths.outDir / "coreProject_NewRoot_nwProject.nwx"
compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx") compFile = tstPaths.refDir / "coreProject_NewRoot_nwProject.nwx"
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
assert theProject.newRoot(nwItemClass.NOVEL) == "0000000000010" assert theProject.newRoot(nwItemClass.NOVEL) == "0000000000010"
assert theProject.newRoot(nwItemClass.PLOT) == "0000000000011" assert theProject.newRoot(nwItemClass.PLOT) == "0000000000011"
@@ -93,16 +91,16 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd): def testCoreProject_NewFileFolder(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
"""Check that new files can be added to the project. """Check that new files can be added to the project.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = fncPath / "nwProject.nwx"
testFile = os.path.join(outDir, "coreProject_NewFileFolder_nwProject.nwx") testFile = tstPaths.outDir / "coreProject_NewFileFolder_nwProject.nwx"
compFile = os.path.join(refDir, "coreProject_NewFileFolder_nwProject.nwx") compFile = tstPaths.refDir / "coreProject_NewFileFolder_nwProject.nwx"
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Invalid call # Invalid call
assert theProject.newFolder("New Folder", "1234567890abc") is None assert theProject.newFolder("New Folder", "1234567890abc") is None
@@ -147,15 +145,15 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
assert "0000000000011" in theProject.tree assert "0000000000011" in theProject.tree
# Delete new files and folders # Delete new files and folders
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000012.nwd")) assert (fncPath / "content" / "0000000000012.nwd").exists()
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000011.nwd")) assert (fncPath / "content" / "0000000000011.nwd").exists()
assert theProject.removeItem("0000000000012") is True assert theProject.removeItem("0000000000012") is True
assert theProject.removeItem("0000000000011") is True assert theProject.removeItem("0000000000011") is True
assert theProject.removeItem("0000000000010") is True assert theProject.removeItem("0000000000010") is True
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000012.nwd")) assert not (fncPath / "content" / "0000000000012.nwd").exists()
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000011.nwd")) assert not (fncPath / "content" / "0000000000011.nwd").exists()
assert "0000000000010" not in theProject.tree assert "0000000000010" not in theProject.tree
assert "0000000000011" not in theProject.tree assert "0000000000011" not in theProject.tree
@@ -167,69 +165,66 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
@pytest.mark.core @pytest.mark.core
def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd): def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
"""Test opening a project. """Test opening a project.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Rename the project file to check handling # Initialising the storage class fails
rName = os.path.join(fncDir, nwFiles.PROJ_FILE)
wName = os.path.join(fncDir, nwFiles.PROJ_FILE+"_sdfghj")
os.rename(rName, wName)
assert theProject.openProject(fncDir) is False
os.rename(wName, rName)
# Fail on folder structure check
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError) mp.setattr("novelwriter.core.storage.NWStorage.openProjectInPlace", lambda *a, **k: False)
shutil.rmtree(os.path.join(fncDir, "meta")) assert theProject.openProject(fncPath) is False
assert theProject.openProject(fncDir) is False
# Fail on lock file # Fail on lock file
assert theProject._storage.writeLockFile() assert theProject._storage.writeLockFile()
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncPath) is False
# Fail to read lockfile (which still opens the project) # Fail to read lockfile (which still opens the project)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.readLockFile", lambda *a: ["ERROR"]) mp.setattr("novelwriter.core.storage.NWStorage.readLockFile", lambda *a: ["ERROR"])
caplog.clear() caplog.clear()
assert theProject.openProject(fncDir) is True assert theProject.openProject(fncPath) is True
assert "Failed to check lock file" in caplog.text assert "Failed to check lock file" in caplog.text
assert theProject.closeProject() assert theProject.closeProject()
# Force open with lockfile # Force open with lockfile
assert theProject._storage.writeLockFile() assert theProject._storage.writeLockFile()
assert theProject.openProject(fncDir, overrideLock=True) is True assert theProject.openProject(fncPath, overrideLock=True) is True
assert theProject.closeProject() assert theProject.closeProject()
# Fail getting xml reader
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.getXmlReader", lambda *a: None)
assert theProject.openProject(fncPath) is False
# Not a novelwriter XML file # Not a novelwriter XML file
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False) mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncPath) is False
assert "Project file does not appear" in mockGUI.lastAlert assert "Project file does not appear" in mockGUI.lastAlert
# Unknown project file version # Unknown project file version
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False) mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION))
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncPath) is False
assert "Unknown or unsupported novelWriter project file" in mockGUI.lastAlert assert "Unknown or unsupported novelWriter project file" in mockGUI.lastAlert
# Other parse error # Other parse error
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False) mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE))
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncPath) is False
assert "Failed to parse project xml" in mockGUI.lastAlert assert "Failed to parse project xml" in mockGUI.lastAlert
# Won't convert legacy file # Won't convert legacy file
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mockGUI.askResponse = False mockGUI.askResponse = False
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncPath) is False
assert "The file format of your project is about to be" in mockGUI.lastQuestion[1] assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
mockGUI.askResponse = True mockGUI.askResponse = True
@@ -237,17 +232,22 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd):
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999)) mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
mockGUI.askResponse = False mockGUI.askResponse = False
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncPath) is False
assert "This project was saved by a newer version" in mockGUI.lastQuestion[1] assert "This project was saved by a newer version" in mockGUI.lastQuestion[1]
mockGUI.askResponse = True mockGUI.askResponse = True
# Fail checking items should still pass
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.tree.NWTree.updateItemData", lambda *a: False)
assert theProject.openProject(fncPath) is True
assert theProject.closeProject() assert theProject.closeProject()
# END Test testCoreProject_Open # END Test testCoreProject_Open
@pytest.mark.core @pytest.mark.core
def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir): def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncPath):
"""Test saving a project. """Test saving a project.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
@@ -256,7 +256,12 @@ def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir):
assert theProject.saveProject() is False assert theProject.saveProject() is False
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Fail getting xml writer
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.getXmlWriter", lambda *a: None)
assert theProject.saveProject() is False
# Fail writing # Fail writing
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -272,11 +277,11 @@ def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_AccessItems(mockGUI, fncDir, mockRnd): def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
"""Test helper functions for the project folder. """Test helper functions for the project folder.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Storage Objects # Storage Objects
assert isinstance(theProject.index, NWIndex) assert isinstance(theProject.index, NWIndex)
@@ -337,12 +342,12 @@ def testCoreProject_AccessItems(mockGUI, fncDir, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd): def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
"""Test the status and importance flag handling. """Test the status and importance flag handling.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished] statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@@ -447,11 +452,11 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Methods(monkeypatch, mockGUI, fncDir, mockRnd): def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test other project class methods and functions. """Test other project class methods and functions.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Project Name # Project Name
theProject.data.setName(" A Name ") theProject.data.setName(" A Name ")
@@ -497,9 +502,12 @@ def testCoreProject_Methods(monkeypatch, mockGUI, fncDir, mockRnd):
# Spell check # Spell check
theProject.setProjectChanged(False) theProject.setProjectChanged(False)
theProject._projAltered = False
theProject.data.setSpellCheck(True) theProject.data.setSpellCheck(True)
theProject.data.setSpellCheck(False) theProject.data.setSpellCheck(False)
assert theProject.projChanged assert theProject.projChanged is True
assert theProject.projAltered is True
assert theProject.projOpened > 0
# Spell language # Spell language
theProject.setProjectChanged(False) theProject.setProjectChanged(False)
@@ -510,7 +518,7 @@ def testCoreProject_Methods(monkeypatch, mockGUI, fncDir, mockRnd):
assert theProject.data.spellLang is None assert theProject.data.spellLang is None
theProject.data.setSpellLang("en_GB") theProject.data.setSpellLang("en_GB")
assert theProject.data.spellLang == "en_GB" assert theProject.data.spellLang == "en_GB"
assert theProject.projChanged assert theProject.projChanged is True
# Project Language # Project Language
theProject.setProjectChanged(False) theProject.setProjectChanged(False)
@@ -524,6 +532,20 @@ def testCoreProject_Methods(monkeypatch, mockGUI, fncDir, mockRnd):
assert theProject.localLookup(1) == "One" assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten" assert theProject.localLookup(10) == "Ten"
# Set invalid language
theProject.data.setLanguage("foo")
theProject._loadProjectLocalisation()
assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten"
# Block reading language data
theProject.data.setLanguage("en")
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
theProject._loadProjectLocalisation()
assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten"
# Last edited # Last edited
theProject.setProjectChanged(False) theProject.setProjectChanged(False)
theProject._data.setLastHandle("0123456789abc", "editor") theProject._data.setLastHandle("0123456789abc", "editor")
@@ -624,7 +646,7 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
assert theProject.closeProject() is True assert theProject.closeProject() is True
# First Item with Meta Data # First Item with Meta Data
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd") orphPath = Path(nwLipsum) / "content" / "636b6aa9b697b.nwd"
writeFile(orphPath, ( writeFile(orphPath, (
"%%~name:[Recovered] Mars\n" "%%~name:[Recovered] Mars\n"
"%%~path:5eaea4e8cdee8/636b6aa9b697b\n" "%%~path:5eaea4e8cdee8/636b6aa9b697b\n"
@@ -634,19 +656,19 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
)) ))
# Second Item without Meta Data # Second Item without Meta Data
orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd") orphPath = Path(nwLipsum) / "content" / "736b6aa9b697b.nwd"
writeFile(orphPath, "\n") writeFile(orphPath, "\n")
# Invalid File Name # Invalid File Name
tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt") tstPath = Path(nwLipsum) / "content" / "636b6aa9b697b.txt"
writeFile(tstPath, "\n") writeFile(tstPath, "\n")
# Invalid File Name # Invalid File Name
tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd") tstPath = Path(nwLipsum) / "content" / "636b6aa9b697bb.nwd"
writeFile(tstPath, "\n") writeFile(tstPath, "\n")
# Invalid File Name # Invalid File Name
tstPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd") tstPath = Path(nwLipsum) / "content" / "abcdefghijklm.nwd"
writeFile(tstPath, "\n") writeFile(tstPath, "\n")
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
@@ -686,16 +708,21 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Backup(monkeypatch, mockGUI, fncDir, tmpDir): def testCoreProject_Backup(monkeypatch, mockGUI, fncPath, tmpPath):
"""Test the automated backup feature of the project class. The test """Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to backupd file and checks that the project XML file is identical to
the original file. the original file.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
buildTestProject(theProject, fncDir)
# Test faulty settings # No Project
assert theProject.backupProject(doNotify=False) is False
buildTestProject(theProject, fncPath)
# Invalid Settings
# ================
# No project # No project
mockGUI.hasProject = False mockGUI.hasProject = False
@@ -707,21 +734,14 @@ def testCoreProject_Backup(monkeypatch, mockGUI, fncDir, tmpDir):
assert theProject.backupProject(doNotify=False) is False assert theProject.backupProject(doNotify=False) is False
# Missing project name # Missing project name
theProject.mainConf.backupPath = tmpDir theProject.mainConf.backupPath = str(tmpPath)
theProject.data.setName("") theProject.data.setName("")
assert theProject.backupProject(doNotify=False) is False assert theProject.backupProject(doNotify=False) is False
# Non-existent folder # Valid Settings
theProject.mainConf.backupPath = os.path.join(tmpDir, "nonexistent") # ==============
theProject.mainConf.backupPath = str(tmpPath)
theProject.data.setName("Test Minimal") theProject.data.setName("Test Minimal")
assert theProject.backupProject(doNotify=False) is False
# Subfolder of project (causes infinite loop in zipping)
theProject.mainConf.backupPath = os.path.join(fncDir, "subdir")
assert theProject.backupProject(doNotify=False) is False
# Set a valid folder
theProject.mainConf.backupPath = tmpDir
# Can't make folder # Can't make folder
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -736,21 +756,21 @@ def testCoreProject_Backup(monkeypatch, mockGUI, fncDir, tmpDir):
# Test correct settings # Test correct settings
assert theProject.backupProject(doNotify=True) is True assert theProject.backupProject(doNotify=True) is True
theFiles = os.listdir(os.path.join(tmpDir, "Test Minimal")) theFiles = list((tmpPath / "Test Minimal").iterdir())
assert len(theFiles) == 1 assert len(theFiles) == 1
theZip = theFiles[0] theZip = theFiles[0].name
assert theZip[:12] == "Backup from " assert theZip[:12] == "Backup from "
assert theZip[-4:] == ".zip" assert theZip[-4:] == ".zip"
# Extract the archive # Extract the archive
with ZipFile(os.path.join(tmpDir, "Test Minimal", theZip), "r") as inZip: with ZipFile(tmpPath / "Test Minimal" / theZip, mode="r") as inZip:
inZip.extractall(os.path.join(tmpDir, "extract")) inZip.extractall(tmpPath / "extract")
# Check that the main project file was restored # Check that the main project file was restored
assert cmpFiles( assert cmpFiles(
os.path.join(fncDir, "nwProject.nwx"), fncPath / "nwProject.nwx",
os.path.join(tmpDir, "extract", "nwProject.nwx") tmpPath / "extract" / "nwProject.nwx"
) )
# END Test testCoreProject_Backup # END Test testCoreProject_Backup
+5 -6
View File
@@ -19,7 +19,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import sys import sys
import pytest import pytest
@@ -63,10 +62,10 @@ def testCoreSpell_FakeEnchant(monkeypatch):
@pytest.mark.core @pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, fncDir): def testCoreSpell_Enchant(monkeypatch, fncPath):
"""Test the pyenchant spell checker. """Test the pyenchant spell checker.
""" """
wList = os.path.join(fncDir, "wordlist.txt") wList = fncPath / "wordlist.txt"
writeFile(wList, "a_word\nb_word\nc_word\n") writeFile(wList, "a_word\nb_word\nc_word\n")
# Break the enchant package, and check error handling # Break the enchant package, and check error handling
@@ -134,13 +133,13 @@ def testCoreSpell_Enchant(monkeypatch, fncDir):
@pytest.mark.core @pytest.mark.core
def testCoreSpell_SessionWords(fncDir): def testCoreSpell_SessionWords(fncPath):
"""Test the handling of the custom word list in the spell checker. """Test the handling of the custom word list in the spell checker.
New project sessions should not inherit the project word list from New project sessions should not inherit the project word list from
other sessions, so this test checks that they don't bleed through. other sessions, so this test checks that they don't bleed through.
""" """
wList1 = os.path.join(fncDir, "wordlist1.txt") wList1 = fncPath / "wordlist1.txt"
wList2 = os.path.join(fncDir, "wordlist2.txt") wList2 = fncPath / "wordlist2.txt"
writeFile(wList1, "a_word\nb_word\nc_word\n") writeFile(wList1, "a_word\nb_word\nc_word\n")
writeFile(wList2, "d_word\ne_word\nf_word\n") writeFile(wList2, "d_word\ne_word\nf_word\n")