+2
-1
@@ -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
|
||||
|
||||
|
||||
+10
-6
@@ -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
|
||||
@@ -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):
|
||||
|
||||
+3
-15
@@ -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
|
||||
|
||||
##
|
||||
|
||||
+67
-70
@@ -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 <project> 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?<br><br>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
|
||||
@@ -1132,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
|
||||
@@ -1154,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)
|
||||
@@ -1196,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:
|
||||
@@ -1210,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.
|
||||
@@ -1243,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.
|
||||
@@ -1292,7 +1282,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 +1342,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.
|
||||
@@ -1363,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
|
||||
|
||||
@@ -1490,7 +1486,8 @@ class NWProject():
|
||||
os.unlink(rmFile)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
# END Class NWProject
|
||||
|
||||
+18
-14
@@ -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
|
||||
|
||||
|
||||
+6
-13
@@ -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
|
||||
@@ -108,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:
|
||||
@@ -136,18 +138,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:
|
||||
|
||||
+11
-9
@@ -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"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h1, hStyle, aNm, tHead, h1))
|
||||
tmpResult.append("<%s%s%s>%s%s</%s>\n" % (h1, h1Cl, hStyle, aNm, tHead, h1))
|
||||
|
||||
elif tType == self.T_HEAD2:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
|
||||
@@ -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
|
||||
|
||||
+15
-5
@@ -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
|
||||
|
||||
+16
-7
@@ -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, skipping" % tHandle)
|
||||
return False
|
||||
|
||||
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,10 +107,13 @@ class NWTree():
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
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
|
||||
@@ -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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+11
-4
@@ -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))
|
||||
@@ -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
|
||||
@@ -1073,10 +1080,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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[pytest]
|
||||
markers =
|
||||
project: Project classes tests
|
||||
error: Test various error handling scenarios
|
||||
core: Core functionality tests
|
||||
gui: Qt5 GUI tests
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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 unit tests 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 | 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` |
|
||||
| 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` |
|
||||
+73
-78
@@ -7,7 +7,7 @@ import pytest
|
||||
import shutil
|
||||
import os
|
||||
|
||||
from nwdummy import DummyMain
|
||||
from dummy import DummyMain
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
@@ -20,94 +20,42 @@ 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.
|
||||
"""
|
||||
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 nwRef():
|
||||
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
|
||||
##
|
||||
theDir = os.path.join(testDir, "reference")
|
||||
return theDir
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tmpConf(nwTemp):
|
||||
"""Create a temporary novelWriter configuration object.
|
||||
def outDir(tmpDir):
|
||||
"""An output folder for test results
|
||||
"""
|
||||
theConf = Config()
|
||||
theConf.initConfig(nwTemp, nwTemp)
|
||||
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 nwDummy(nwRef, nwTemp, nwConf):
|
||||
"""Create a dummy instance of novelWriter's main GUI class.
|
||||
"""
|
||||
theDummy = DummyMain()
|
||||
theDummy.mainConf = nwConf
|
||||
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 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
|
||||
theDir = os.path.join(tmpDir, "results")
|
||||
if not os.path.isdir(theDir):
|
||||
os.mkdir(theDir)
|
||||
return theDir
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwFuncTemp(nwTemp):
|
||||
def fncDir(tmpDir):
|
||||
"""A temporary folder for a single test function.
|
||||
"""
|
||||
funcDir = os.path.join(nwTemp, "ftemp")
|
||||
funcDir = os.path.join(tmpDir, "ftemp")
|
||||
if os.path.isdir(funcDir):
|
||||
shutil.rmtree(funcDir)
|
||||
if not os.path.isdir(funcDir):
|
||||
@@ -118,16 +66,37 @@ def nwFuncTemp(nwTemp):
|
||||
return
|
||||
|
||||
##
|
||||
# Temp Folders for Projects
|
||||
# novelWriter Objects
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwMinimal(nwTemp):
|
||||
def tmpConf(tmpDir):
|
||||
"""Create a temporary novelWriter configuration object.
|
||||
"""
|
||||
theConf = Config()
|
||||
theConf.initConfig(tmpDir, tmpDir)
|
||||
theConf.setLastPath("")
|
||||
return theConf
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def dummyGUI(tmpConf):
|
||||
"""Create a dummy instance of novelWriter's main GUI class.
|
||||
"""
|
||||
theDummy = DummyMain()
|
||||
theDummy.mainConf = tmpConf
|
||||
return theDummy
|
||||
|
||||
##
|
||||
# Temp Project Folders
|
||||
##
|
||||
|
||||
@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(nwTemp, "minimal")
|
||||
minimalDir = os.path.join(tmpDir, "minimal")
|
||||
if os.path.isdir(minimalDir):
|
||||
shutil.rmtree(minimalDir)
|
||||
shutil.copytree(minimalStore, minimalDir)
|
||||
@@ -143,13 +112,13 @@ def nwMinimal(nwTemp):
|
||||
return
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwLipsum(nwTemp):
|
||||
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(nwTemp, "lipsum")
|
||||
lipsumDir = os.path.join(tmpDir, "lipsum")
|
||||
if os.path.isdir(lipsumDir):
|
||||
shutil.rmtree(lipsumDir)
|
||||
shutil.copytree(lipsumStore, lipsumDir)
|
||||
@@ -165,12 +134,12 @@ def nwLipsum(nwTemp):
|
||||
return
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwOldProj(nwTemp):
|
||||
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(nwTemp, "oldproj")
|
||||
oldProjDir = os.path.join(tmpDir, "oldproj")
|
||||
if os.path.isdir(oldProjDir):
|
||||
shutil.rmtree(oldProjDir)
|
||||
shutil.copytree(oldProjStore, oldProjDir)
|
||||
@@ -199,4 +168,30 @@ def yesToAll(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes
|
||||
)
|
||||
yield
|
||||
monkeypatch.undo()
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
|
||||
##
|
||||
# Temporary Test Folders
|
||||
##
|
||||
|
||||
@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
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Test Dummy GUI Classes
|
||||
"""
|
||||
|
||||
# =========================================================================== #
|
||||
# Mock GUI
|
||||
# =========================================================================== #
|
||||
|
||||
class DummyMain():
|
||||
|
||||
def __init__(self):
|
||||
self.mainConf = None
|
||||
self.hasProject = True
|
||||
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
|
||||
|
||||
def setProjectStatus(self, isChanged):
|
||||
return
|
||||
|
||||
def openProject(self, projPath):
|
||||
return
|
||||
|
||||
def rebuildIndex(self):
|
||||
return
|
||||
|
||||
# Test Functions
|
||||
|
||||
def undo(self):
|
||||
self.askResponse = True
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
self.lastAlert = ""
|
||||
return
|
||||
|
||||
# END Class GuiMain
|
||||
|
||||
class StatusBar():
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
def setStatus(self, theText):
|
||||
return
|
||||
|
||||
# END Class StatusBar
|
||||
|
||||
# =========================================================================== #
|
||||
# Error Functions
|
||||
# Dummy functions that will raise errors instead.
|
||||
# =========================================================================== #
|
||||
|
||||
def causeOSError(*args, **kwargs):
|
||||
raise OSError
|
||||
@@ -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"
|
||||
]
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
[
|
||||
[
|
||||
"content/8c659a11cd429.nwd",
|
||||
"NOVEL",
|
||||
"New Scene"
|
||||
],
|
||||
[
|
||||
"content/a35baf2e93843.nwd",
|
||||
"NOVEL",
|
||||
"Title Page"
|
||||
],
|
||||
[
|
||||
"content/f5ab3e30151e1.nwd",
|
||||
"NOVEL",
|
||||
"New Chapter"
|
||||
]
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Test Dummy GUI Classes
|
||||
"""
|
||||
|
||||
class DummyMain():
|
||||
|
||||
def __init__(self):
|
||||
self.mainConf = None
|
||||
self.hasProject = True
|
||||
self.statusBar = StatusBar()
|
||||
return
|
||||
|
||||
def makeAlert(self, theMessage, theLevel):
|
||||
print("%s: %s" % (str(theLevel), theMessage))
|
||||
return
|
||||
|
||||
def setStatus(self, theMessage):
|
||||
return
|
||||
|
||||
def setProjectStatus(self, isChanged):
|
||||
return
|
||||
|
||||
def openProject(self, projPath):
|
||||
return
|
||||
|
||||
def rebuildIndex(self):
|
||||
return
|
||||
|
||||
# END Class GuiMain
|
||||
|
||||
class StatusBar():
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
def setStatus(self, theText):
|
||||
return
|
||||
|
||||
# END Class StatusBar
|
||||
@@ -39,35 +39,35 @@
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="8">
|
||||
<item handle="73475cb40a568" order="None" parent="None">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="44cb730c42048" order="None" parent="None">
|
||||
<item handle="44cb730c42048" order="0" parent="None">
|
||||
<name>Plot</name>
|
||||
<type>ROOT</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" order="None" parent="None">
|
||||
<item handle="71ee45a3c0db9" order="0" parent="None">
|
||||
<name>Characters</name>
|
||||
<type>ROOT</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="None" parent="None">
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>World</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -79,14 +79,14 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="None" parent="73475cb40a568">
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="None" parent="31489056e0916">
|
||||
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
||||
<name>New Chapter</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -98,7 +98,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" order="None" parent="31489056e0916">
|
||||
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
|
||||
<name>New Scene</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
+23
-23
@@ -41,56 +41,56 @@
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="23">
|
||||
<item handle="73475cb40a568" order="None" parent="None">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="44cb730c42048" order="None" parent="None">
|
||||
<item handle="44cb730c42048" order="0" parent="None">
|
||||
<name>Plot</name>
|
||||
<type>ROOT</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" order="None" parent="None">
|
||||
<item handle="71ee45a3c0db9" order="0" parent="None">
|
||||
<name>Characters</name>
|
||||
<type>ROOT</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="None" parent="None">
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>Locations</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" order="None" parent="None">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="None">
|
||||
<name>Timeline</name>
|
||||
<type>ROOT</type>
|
||||
<class>TIMELINE</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="None" parent="None">
|
||||
<item handle="31489056e0916" order="0" parent="None">
|
||||
<name>Objects</name>
|
||||
<type>ROOT</type>
|
||||
<class>OBJECT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="None" parent="None">
|
||||
<item handle="98010bd9270f9" order="0" parent="None">
|
||||
<name>Entity</name>
|
||||
<type>ROOT</type>
|
||||
<class>ENTITY</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" order="None" parent="73475cb40a568">
|
||||
<item handle="0e17daca5f3e1" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -102,14 +102,14 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="None" parent="73475cb40a568">
|
||||
<item handle="1a6562590ef19" order="0" parent="73475cb40a568">
|
||||
<name>Chapter 1</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="None" parent="1a6562590ef19">
|
||||
<item handle="031b4af5197ec" order="0" parent="1a6562590ef19">
|
||||
<name>Chapter 1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -121,7 +121,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" order="None" parent="1a6562590ef19">
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -133,7 +133,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" order="None" parent="1a6562590ef19">
|
||||
<item handle="2858dcd1057d3" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.2</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -145,7 +145,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="2fca346db6561" order="None" parent="1a6562590ef19">
|
||||
<item handle="2fca346db6561" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -157,14 +157,14 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="02d20bbd7e394" order="None" parent="73475cb40a568">
|
||||
<item handle="02d20bbd7e394" order="0" parent="73475cb40a568">
|
||||
<name>Chapter 2</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="7688b6ef52555" order="None" parent="02d20bbd7e394">
|
||||
<item handle="7688b6ef52555" order="0" parent="02d20bbd7e394">
|
||||
<name>Chapter 2</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -176,7 +176,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="c837649cce43f" order="None" parent="02d20bbd7e394">
|
||||
<item handle="c837649cce43f" order="0" parent="02d20bbd7e394">
|
||||
<name>Scene 2.1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -188,7 +188,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="6208ef0f7750c" order="None" parent="02d20bbd7e394">
|
||||
<item handle="6208ef0f7750c" order="0" parent="02d20bbd7e394">
|
||||
<name>Scene 2.2</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -200,7 +200,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="3e1e967e9b793" order="None" parent="02d20bbd7e394">
|
||||
<item handle="3e1e967e9b793" order="0" parent="02d20bbd7e394">
|
||||
<name>Scene 2.3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -212,14 +212,14 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="39fa9ec190eee" order="None" parent="73475cb40a568">
|
||||
<item handle="39fa9ec190eee" order="0" parent="73475cb40a568">
|
||||
<name>Chapter 3</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="d029fa3a95e17" order="None" parent="39fa9ec190eee">
|
||||
<item handle="d029fa3a95e17" order="0" parent="39fa9ec190eee">
|
||||
<name>Chapter 3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -231,7 +231,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="81b8a03f97e87" order="None" parent="39fa9ec190eee">
|
||||
<item handle="81b8a03f97e87" order="0" parent="39fa9ec190eee">
|
||||
<name>Scene 3.1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -243,7 +243,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="da4ea2a5506f2" order="None" parent="39fa9ec190eee">
|
||||
<item handle="da4ea2a5506f2" order="0" parent="39fa9ec190eee">
|
||||
<name>Scene 3.2</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -255,7 +255,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="a68b412c42825" order="None" parent="39fa9ec190eee">
|
||||
<item handle="a68b412c42825" order="0" parent="39fa9ec190eee">
|
||||
<name>Scene 3.3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
+14
-14
@@ -41,56 +41,56 @@
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="14">
|
||||
<item handle="73475cb40a568" order="None" parent="None">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="44cb730c42048" order="None" parent="None">
|
||||
<item handle="44cb730c42048" order="0" parent="None">
|
||||
<name>Plot</name>
|
||||
<type>ROOT</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" order="None" parent="None">
|
||||
<item handle="71ee45a3c0db9" order="0" parent="None">
|
||||
<name>Characters</name>
|
||||
<type>ROOT</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="None" parent="None">
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>Locations</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" order="None" parent="None">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="None">
|
||||
<name>Timeline</name>
|
||||
<type>ROOT</type>
|
||||
<class>TIMELINE</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="None" parent="None">
|
||||
<item handle="31489056e0916" order="0" parent="None">
|
||||
<name>Objects</name>
|
||||
<type>ROOT</type>
|
||||
<class>OBJECT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="None" parent="None">
|
||||
<item handle="98010bd9270f9" order="0" parent="None">
|
||||
<name>Entity</name>
|
||||
<type>ROOT</type>
|
||||
<class>ENTITY</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" order="None" parent="73475cb40a568">
|
||||
<item handle="0e17daca5f3e1" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -102,7 +102,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="None" parent="73475cb40a568">
|
||||
<item handle="1a6562590ef19" order="0" parent="73475cb40a568">
|
||||
<name>Scene 1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -114,7 +114,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="None" parent="73475cb40a568">
|
||||
<item handle="031b4af5197ec" order="0" parent="73475cb40a568">
|
||||
<name>Scene 2</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -126,7 +126,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" order="None" parent="73475cb40a568">
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="73475cb40a568">
|
||||
<name>Scene 3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -138,7 +138,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" order="None" parent="73475cb40a568">
|
||||
<item handle="2858dcd1057d3" order="0" parent="73475cb40a568">
|
||||
<name>Scene 4</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -150,7 +150,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="2fca346db6561" order="None" parent="73475cb40a568">
|
||||
<item handle="2fca346db6561" order="0" parent="73475cb40a568">
|
||||
<name>Scene 5</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -162,7 +162,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="02d20bbd7e394" order="None" parent="73475cb40a568">
|
||||
<item handle="02d20bbd7e394" order="0" parent="73475cb40a568">
|
||||
<name>Scene 6</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
+12
-12
@@ -39,35 +39,35 @@
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="12">
|
||||
<item handle="73475cb40a568" order="None" parent="None">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="44cb730c42048" order="None" parent="None">
|
||||
<item handle="44cb730c42048" order="0" parent="None">
|
||||
<name>Plot</name>
|
||||
<type>ROOT</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" order="None" parent="None">
|
||||
<item handle="71ee45a3c0db9" order="0" parent="None">
|
||||
<name>Characters</name>
|
||||
<type>ROOT</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="None" parent="None">
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>World</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -79,14 +79,14 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="None" parent="73475cb40a568">
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="None" parent="31489056e0916">
|
||||
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
||||
<name>New Chapter</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -98,7 +98,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" order="None" parent="31489056e0916">
|
||||
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
|
||||
<name>New Scene</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -110,28 +110,28 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="None" parent="None">
|
||||
<item handle="1a6562590ef19" order="0" parent="None">
|
||||
<name>Timeline</name>
|
||||
<type>ROOT</type>
|
||||
<class>TIMELINE</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="None" parent="None">
|
||||
<item handle="031b4af5197ec" order="0" parent="None">
|
||||
<name>Object</name>
|
||||
<type>ROOT</type>
|
||||
<class>OBJECT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" order="None" parent="None">
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="None">
|
||||
<name>Custom1</name>
|
||||
<type>ROOT</type>
|
||||
<class>CUSTOM</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" order="None" parent="None">
|
||||
<item handle="2858dcd1057d3" order="0" parent="None">
|
||||
<name>Custom2</name>
|
||||
<type>ROOT</type>
|
||||
<class>CUSTOM</class>
|
||||
+10
-10
@@ -39,35 +39,35 @@
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="10">
|
||||
<item handle="73475cb40a568" order="None" parent="None">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="44cb730c42048" order="None" parent="None">
|
||||
<item handle="44cb730c42048" order="0" parent="None">
|
||||
<name>Plot</name>
|
||||
<type>ROOT</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" order="None" parent="None">
|
||||
<item handle="71ee45a3c0db9" order="0" parent="None">
|
||||
<name>Characters</name>
|
||||
<type>ROOT</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="None" parent="None">
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>World</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -79,14 +79,14 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="None" parent="73475cb40a568">
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="None" parent="31489056e0916">
|
||||
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
||||
<name>New Chapter</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -98,7 +98,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" order="None" parent="31489056e0916">
|
||||
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
|
||||
<name>New Scene</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -110,7 +110,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="None" parent="31489056e0916">
|
||||
<item handle="1a6562590ef19" order="0" parent="31489056e0916">
|
||||
<name>Hello</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -122,7 +122,7 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="None" parent="71ee45a3c0db9">
|
||||
<item handle="031b4af5197ec" order="0" parent="71ee45a3c0db9">
|
||||
<name>Jane</name>
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
@@ -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():
|
||||
|
||||
+24
-24
@@ -5,14 +5,14 @@
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from nwtools import cmpFiles
|
||||
from tools import cmpFiles
|
||||
|
||||
@pytest.mark.core
|
||||
def testConfigCore(tmpConf, nwTemp, 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 == 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):
|
||||
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
|
||||
|
||||
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):
|
||||
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 == 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):
|
||||
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 == 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):
|
||||
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 == 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, refDir):
|
||||
assert tmpConf.confPath == tmpDir
|
||||
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.getTextWidth() == 1200
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter NWDoc Class Tester
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
monkeypatch.setattr("builtins.open", causeOSError)
|
||||
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", causeOSError)
|
||||
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
|
||||
@@ -8,28 +8,28 @@ 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
|
||||
from nw.constants import nwItemClass, nwItemLayout
|
||||
|
||||
@pytest.mark.project
|
||||
def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef):
|
||||
@pytest.mark.core
|
||||
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, "1_tagsIndex.json")
|
||||
refFile = os.path.join(nwRef, "proj", "1_tagsIndex.json")
|
||||
testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json")
|
||||
compFile = os.path.join(refDir, "coreIndex_LoadSave_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
|
||||
@@ -136,17 +136,19 @@ def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef):
|
||||
assert theProject.closeProject()
|
||||
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, refFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
@pytest.mark.project
|
||||
def testIndexScanThis(nwMinimal, nwDummy):
|
||||
# END Test testCoreIndex_LoadSave
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ScanThis(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
|
||||
@@ -162,40 +164,42 @@ def testIndexScanThis(nwMinimal, nwDummy):
|
||||
|
||||
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, nwDummy):
|
||||
# END Test testCoreIndex_ScanThis
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_CheckThese(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]
|
||||
@@ -209,29 +213,32 @@ def testIndexCheckThese(nwMinimal, nwDummy):
|
||||
"# 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, nwDummy):
|
||||
"""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(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")
|
||||
@@ -392,21 +399,23 @@ def testIndexScanText(nwMinimal, nwDummy):
|
||||
"% 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, nwDummy):
|
||||
# END Test testCoreIndex_ScanText
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ExtractData(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")
|
||||
|
||||
@@ -424,7 +433,13 @@ def testIndexExtractData(nwMinimal, nwDummy):
|
||||
))
|
||||
|
||||
# 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, nwDummy):
|
||||
|
||||
# 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, nwDummy):
|
||||
|
||||
# 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, nwDummy):
|
||||
assert pC == 2
|
||||
|
||||
assert theProject.closeProject()
|
||||
|
||||
# END Test testCoreIndex_ExtractData
|
||||
@@ -6,13 +6,15 @@ 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.project
|
||||
def testItemSettersSimple(nwDummy):
|
||||
|
||||
theProject = NWProject(nwDummy)
|
||||
@pytest.mark.core
|
||||
def testCoreItem_Setters(dummyGUI):
|
||||
"""Test all the simple setters for the NWItem class.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theItem = NWItem(theProject)
|
||||
|
||||
# Name
|
||||
@@ -20,12 +22,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 +42,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 +67,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 +94,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 +140,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 testCoreItem_Setters
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreItem_TypeSetter(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 testCoreItem_TypeSetter
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreItem_ClassSetter(dummyGUI):
|
||||
"""Test the setter for all the nwItemClass values for the NWItem
|
||||
class.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theItem = NWItem(theProject)
|
||||
|
||||
# Class
|
||||
@@ -138,33 +210,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 testCoreItem_ClassSetter
|
||||
|
||||
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 testCoreItem_LayoutSetter(dummyGUI):
|
||||
"""Test the setter for all the nwItemLayout values for the NWItem
|
||||
class.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theItem = NWItem(theProject)
|
||||
|
||||
# Layout
|
||||
@@ -190,14 +246,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 testCoreItem_LayoutSetter
|
||||
|
||||
theProject = NWProject(nwDummy)
|
||||
theItem = NWItem(theProject)
|
||||
@pytest.mark.core
|
||||
def testCoreItem_XMLPackUnpack(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 +270,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 +283,18 @@ def testItemXMLPackUnpack(nwDummy):
|
||||
b"<content>"
|
||||
b"<item handle=\"0123456789abc\" order=\"1\" parent=\"0123456789abc\">"
|
||||
b"<name>A Name</name><type>FILE</type><class>NOVEL</class><status>New</status>"
|
||||
b"<exported>True</exported><layout>NOTE</layout><charCount>7</charCount>"
|
||||
b"<exported>False</exported><layout>NOTE</layout><charCount>7</charCount>"
|
||||
b"<wordCount>5</wordCount><paraCount>3</paraCount><cursorPos>11</cursorPos></item>"
|
||||
b"</content>"
|
||||
)
|
||||
|
||||
# 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 +303,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"<content>"
|
||||
b"<item handle=\"0123456789abc\" order=\"1\" parent=\"0123456789abc\">"
|
||||
b"<name>A Name</name><type>FOLDER</type><class>NOVEL</class><status>New</status>"
|
||||
b"<expanded>True</expanded></item>"
|
||||
b"</content>"
|
||||
)
|
||||
|
||||
# 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 +379,5 @@ def testItemXMLPackUnpack(nwDummy):
|
||||
assert etree.tostring(xDummy, pretty_print=False, encoding="utf-8") == (
|
||||
b"<group/>"
|
||||
)
|
||||
|
||||
# END Test testCoreItem_XMLPackUnpack
|
||||
@@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter OptionState Class Tester
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
monkeypatch.setattr("builtins.open", causeOSError)
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Spell Check Class Tester
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
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
|
||||
@@ -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"<status>"
|
||||
b"<entry blue=\"100\" green=\"100\" red=\"100\">New</entry>"
|
||||
b"<entry blue=\"0\" green=\"50\" red=\"200\">Minor</entry>"
|
||||
b"<entry blue=\"0\" green=\"150\" red=\"200\">Major</entry>"
|
||||
b"<entry blue=\"0\" green=\"200\" red=\"50\">Main</entry>"
|
||||
b"</status>"
|
||||
)
|
||||
|
||||
# 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
|
||||
@@ -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") == (
|
||||
"<p class='synopsis'><strong>Synopsis: </strong>synopsis text</p>\n"
|
||||
)
|
||||
assert theHtml._formatComments("comment text") == (
|
||||
"<p class='comment'><strong>Comment: </strong>comment text</p>\n"
|
||||
)
|
||||
|
||||
assert theHtml._formatKeywords("") == ""
|
||||
assert theHtml._formatKeywords("tag: Jane") == (
|
||||
"<div><span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a></div>"
|
||||
)
|
||||
assert theHtml._formatKeywords("char: Bod, Jane") == (
|
||||
"<div>"
|
||||
"<span class='tags'>Characters:</span> "
|
||||
"<a href='#tag_Bod'>Bod</a>, "
|
||||
"<a href='#tag_Jane'>Jane</a>"
|
||||
"</div>"
|
||||
)
|
||||
|
||||
# Preview Mode
|
||||
# ============
|
||||
|
||||
theHtml.setPreview(True, True)
|
||||
|
||||
assert theHtml._formatSynopsis("synopsis text") == (
|
||||
"<p class='comment'><span class='synopsis'>Synopsis: </span>synopsis text</p>\n"
|
||||
)
|
||||
assert theHtml._formatComments("comment text") == (
|
||||
"<p class='comment'>comment text</p>\n"
|
||||
)
|
||||
|
||||
assert theHtml._formatKeywords("") == ""
|
||||
assert theHtml._formatKeywords("tag: Jane") == (
|
||||
"<div><span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a></div>"
|
||||
)
|
||||
assert theHtml._formatKeywords("char: Bod, Jane") == (
|
||||
"<div>"
|
||||
"<span class='tags'>Characters:</span> "
|
||||
"<a href='#char=Bod'>Bod</a>, "
|
||||
"<a href='#char=Jane'>Jane</a>"
|
||||
"</div>"
|
||||
)
|
||||
|
||||
# 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 == "<h1 class='title'>Title</h1>\n"
|
||||
|
||||
# Header 2
|
||||
theHtml.theText = "## Chapter Title\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h1>Chapter Title</h1>\n"
|
||||
|
||||
# Header 3
|
||||
theHtml.theText = "### Scene Title\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h2>Scene Title</h2>\n"
|
||||
|
||||
# Header 4
|
||||
theHtml.theText = "#### Section Title\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h3>Section Title</h3>\n"
|
||||
|
||||
theHtml.isNovel = False
|
||||
theHtml.setLinkHeaders(True)
|
||||
|
||||
# Header 1
|
||||
theHtml.theText = "# Heading One\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h1><a name='T000001'></a>Heading One</h1>\n"
|
||||
|
||||
# Header 2
|
||||
theHtml.theText = "## Heading Two\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h2><a name='T000001'></a>Heading Two</h2>\n"
|
||||
|
||||
# Header 3
|
||||
theHtml.theText = "### Heading Three\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h3><a name='T000001'></a>Heading Three</h3>\n"
|
||||
|
||||
# Header 4
|
||||
theHtml.theText = "#### Heading Four\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == "<h4><a name='T000001'></a>Heading Four</h4>\n"
|
||||
|
||||
# Text
|
||||
theHtml.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<p>Some <strong>nested bold and <em>italic</em> and "
|
||||
"<del>strikethrough</del> text</strong> here</p>\n"
|
||||
)
|
||||
|
||||
# Text w/Hard Break
|
||||
theHtml.theText = "Line one \nLine two \nLine three\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<p class='break'>Line one<br/>Line two<br/>Line three</p>\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 == (
|
||||
"<p class='synopsis'><strong>Synopsis: </strong>The synopsis ...</p>\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 == (
|
||||
"<p class='comment'><strong>Comment: </strong>A comment ...</p>\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 == (
|
||||
"<div><span class='tags'>Characters:</span> "
|
||||
"<a href='#tag_Bod'>Bod</a>, <a href='#tag_Jane'>Jane</a></div>"
|
||||
)
|
||||
|
||||
# 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 == (
|
||||
"<h1 class='title' style='text-align: center; page-break-before: never;'>"
|
||||
"<a name='T000001'></a>A Title</h1>\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 == "<p class='sep'>* * *</p>\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 == "<p class='skip'> </p>\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 == (
|
||||
"<h1 class='title'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
theHtml.setStyles(True)
|
||||
|
||||
# Align Left
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' style='text-align: left;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Align Right
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_RIGHT),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' style='text-align: right;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Align Centre
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_CENTRE),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' style='text-align: center;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Align Justify
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_JUSTIFY),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' style='text-align: justify;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Page Break Always
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB | theHtml.A_PBA),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' "
|
||||
"style='page-break-before: always; page-break-after: always;'>A Title</h1>\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 == (
|
||||
"<h1 class='title' "
|
||||
"style='page-break-before: avoid; page-break-after: avoid;'>A Title</h1>\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 == (
|
||||
"<h1 class='title' "
|
||||
"style='page-break-before: never; page-break-after: never;'>A Title</h1>\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 == (
|
||||
"<p>Some <b>nested bold and <i>italic</i> and "
|
||||
"<span style='text-decoration: line-through;'>strikethrough</span> "
|
||||
"text</b> here</p>\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 <brackets> & short–dash, long—dash …\n"
|
||||
theHtml.theText = docText
|
||||
theHtml.doAutoReplace()
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<p>Text with <brackets> & short–dash, long—dash …</p>\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
|
||||
@@ -0,0 +1,644 @@
|
||||
# -*- 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(theProject, dummyGUI)
|
||||
|
||||
# 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 <A> and <B>.\n\n"
|
||||
)
|
||||
docTextR = docText.replace("<A>", "this").replace("<B>", "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.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
|
||||
@@ -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
|
||||
@@ -0,0 +1,433 @@
|
||||
# -*- 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="function")
|
||||
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)
|
||||
assert 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
|
||||
|
||||
assert not 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
|
||||
|
||||
assert theTree.append(None, None, itemT)
|
||||
assert len(theTree) == len(dummyItems) + 1
|
||||
|
||||
theList = theTree.handles()
|
||||
assert theList[-1] == "73475cb40a568"
|
||||
|
||||
# Try to add existing handle
|
||||
assert not theTree.append("73475cb40a568", None, itemT)
|
||||
assert len(theTree) == len(dummyItems) + 1
|
||||
|
||||
# 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
|
||||
|
||||
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(monkeypatch, dummyGUI):
|
||||
"""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"<novelWriterXML>"
|
||||
b"<content count=\"8\">"
|
||||
b"<item handle=\"a000000000001\" order=\"0\" parent=\"None\">"
|
||||
b"<name>Novel</name><type>ROOT</type><class>NOVEL</class><status>None</status>"
|
||||
b"<expanded>True</expanded></item>"
|
||||
b"<item handle=\"b000000000001\" order=\"0\" parent=\"a000000000001\">"
|
||||
b"<name>Act One</name><type>FOLDER</type><class>NOVEL</class><status>None</status>"
|
||||
b"<expanded>True</expanded></item>"
|
||||
b"<item handle=\"c000000000001\" order=\"0\" parent=\"b000000000001\">"
|
||||
b"<name>Chapter One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
|
||||
b"<exported>True</exported><layout>CHAPTER</layout><charCount>300</charCount>"
|
||||
b"<wordCount>50</wordCount><paraCount>2</paraCount><cursorPos>0</cursorPos></item>"
|
||||
b"<item handle=\"c000000000002\" order=\"0\" parent=\"b000000000001\">"
|
||||
b"<name>Scene One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
|
||||
b"<exported>True</exported><layout>SCENE</layout><charCount>3000</charCount>"
|
||||
b"<wordCount>500</wordCount><paraCount>20</paraCount><cursorPos>0</cursorPos></item>"
|
||||
b"<item handle=\"a000000000002\" order=\"0\" parent=\"None\">"
|
||||
b"<name>Outtakes</name><type>ROOT</type><class>ARCHIVE</class><status>None</status>"
|
||||
b"<expanded>False</expanded></item>"
|
||||
b"<item handle=\"a000000000003\" order=\"0\" parent=\"None\">"
|
||||
b"<name>Trash</name><type>TRASH</type><class>TRASH</class><status>None</status>"
|
||||
b"<expanded>False</expanded></item>"
|
||||
b"<item handle=\"a000000000004\" order=\"0\" parent=\"None\">"
|
||||
b"<name>Characters</name><type>ROOT</type><class>CHARACTER</class><status>None</status>"
|
||||
b"<expanded>True</expanded></item>"
|
||||
b"<item handle=\"b000000000002\" order=\"0\" parent=\"a000000000002\">"
|
||||
b"<name>Jane Doe</name><type>FILE</type><class>CHARACTER</class><status>None</status>"
|
||||
b"<exported>True</exported><layout>NOTE</layout><charCount>2000</charCount>"
|
||||
b"<wordCount>400</wordCount><paraCount>16</paraCount><cursorPos>0</cursorPos></item>"
|
||||
b"</content></novelWriterXML>"
|
||||
)
|
||||
|
||||
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(monkeypatch, dummyGUI, dummyItems, tmpDir):
|
||||
"""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")
|
||||
|
||||
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
|
||||
assert not theTree.writeToCFile()
|
||||
|
||||
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"
|
||||
"Table of Contents\n"
|
||||
"=================\n"
|
||||
"\n"
|
||||
"File Name Class Layout Document Label\n"
|
||||
"-------------------------------------------------------------\n"
|
||||
f"{pathA} NOVEL CHAPTER Chapter One\n"
|
||||
f"{pathB} NOVEL SCENE Scene One\n"
|
||||
f"{pathC} CHARACTER NOTE Jane Doe\n"
|
||||
)
|
||||
|
||||
# END Test testCoreTree_ToCFile
|
||||
+2
-2
@@ -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, fncDir, tmpDir):
|
||||
qApp.closeAllWindows()
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.waitForWindowShown(nwGUI)
|
||||
|
||||
@@ -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 (
|
||||
@@ -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, fncDir, nwTempGUI, refDir, tmpDir):
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.waitForWindowShown(nwGUI)
|
||||
@@ -43,8 +43,8 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR
|
||||
|
||||
# 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,9 +135,9 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR
|
||||
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(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,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, fncDir, nwTempGUI, refDir, tmpDir):
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.waitForWindowShown(nwGUI)
|
||||
@@ -154,7 +154,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, n
|
||||
|
||||
# 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,9 +208,9 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, n
|
||||
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(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])
|
||||
|
||||
@@ -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, fncDir, tmpDir):
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.waitForWindowShown(nwGUI)
|
||||
@@ -227,13 +227,13 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTemp):
|
||||
|
||||
# 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, nwTemp):
|
||||
)
|
||||
|
||||
# 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, nwTemp):
|
||||
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, nwTemp):
|
||||
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, nwTemp):
|
||||
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, nwTemp):
|
||||
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, nwTemp):
|
||||
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, nwTemp):
|
||||
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, 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, fncDir, tmpDir):
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--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, refDir, 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)
|
||||
@@ -428,13 +428,13 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
||||
|
||||
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, nwTemp):
|
||||
|
||||
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, nwTemp):
|
||||
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, nwTemp):
|
||||
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, nwTemp):
|
||||
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,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, refDir, 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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, refDir, tmpConf):
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.waitForWindowShown(nwGUI)
|
||||
@@ -1203,9 +1203,9 @@ def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp, 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(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()
|
||||
@@ -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
|
||||
@@ -27,11 +27,11 @@ typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
@pytest.mark.gui
|
||||
def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp):
|
||||
def testLaunch(qtbot, monkeypatch, fncDir, tmpDir):
|
||||
|
||||
# Defaults
|
||||
nwGUI = nw.main(
|
||||
["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp, "--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, nwTemp):
|
||||
|
||||
# Log Levels
|
||||
nwGUI = nw.main(
|
||||
["--testmode", "--info", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp]
|
||||
["--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" % nwTemp]
|
||||
["--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" % nwTemp]
|
||||
["--testmode", "--verbose", "--config=%s" % fncDir, "--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" % fncDir, "--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" % fncDir, "--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" % fncDir, "--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" % fncDir, "--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, fncDir, nwTempGUI, refDir, 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)
|
||||
@@ -113,7 +113,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
||||
|
||||
# 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,9 +130,9 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
||||
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(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)
|
||||
@@ -140,7 +140,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
||||
# 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, nwRef, nwTemp):
|
||||
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,33 +379,33 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
||||
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(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")
|
||||
projFile = os.path.join(fncDir, "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")
|
||||
projFile = os.path.join(fncDir, "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")
|
||||
projFile = os.path.join(fncDir, "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")
|
||||
projFile = os.path.join(fncDir, "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)
|
||||
|
||||
@@ -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,15 +1013,15 @@ 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, fncDir, tmpDir):
|
||||
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.waitForWindowShown(nwGUI)
|
||||
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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1205,16 +1205,16 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, nwTemp):
|
||||
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()
|
||||
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()
|
||||
@@ -1,679 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Project Class Tester
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from shutil import copyfile
|
||||
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
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
||||
"""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.projTree.setSeed(42)
|
||||
|
||||
# Setting no data should fail
|
||||
assert not theProject.newProject({})
|
||||
|
||||
# Try again with a proper path
|
||||
assert theProject.newProject({"projPath": nwFuncTemp})
|
||||
assert theProject.setProjectPath(nwFuncTemp)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
|
||||
# Creating the project once more should fail
|
||||
assert not theProject.newProject({"projPath": nwFuncTemp})
|
||||
|
||||
# Check the new project
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||
|
||||
# Open again
|
||||
assert theProject.openProject(projFile)
|
||||
|
||||
# Save and close
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||
assert not theProject.projChanged
|
||||
|
||||
# Open a second time
|
||||
assert theProject.openProject(projFile)
|
||||
assert not theProject.openProject(projFile)
|
||||
assert theProject.openProject(projFile, overrideLock=True)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||
"""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.projTree.setSeed(42)
|
||||
|
||||
assert theProject.newProject({"projPath": nwFuncTemp})
|
||||
assert theProject.setProjectPath(nwFuncTemp)
|
||||
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, refFile, [2, 6, 7, 8])
|
||||
assert not theProject.projChanged
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||
"""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.projTree.setSeed(42)
|
||||
|
||||
assert theProject.newProject({"projPath": nwFuncTemp})
|
||||
assert theProject.setProjectPath(nwFuncTemp)
|
||||
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, refFile, [2, 6, 7, 8])
|
||||
assert not theProject.projChanged
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||
"""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")
|
||||
|
||||
projData = {
|
||||
"projName": "Test Custom",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": nwFuncTemp,
|
||||
"popSample": False,
|
||||
"popMinimal": False,
|
||||
"popCustom": True,
|
||||
"addRoots": [
|
||||
nwItemClass.PLOT,
|
||||
nwItemClass.CHARACTER,
|
||||
nwItemClass.WORLD,
|
||||
nwItemClass.TIMELINE,
|
||||
nwItemClass.OBJECT,
|
||||
nwItemClass.ENTITY,
|
||||
],
|
||||
"numChapters": 3,
|
||||
"numScenes": 3,
|
||||
"chFolders": True,
|
||||
}
|
||||
theProject = NWProject(nwDummy)
|
||||
theProject.projTree.setSeed(42)
|
||||
|
||||
assert theProject.newProject(projData)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||
"""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")
|
||||
|
||||
projData = {
|
||||
"projName": "Test Custom",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": nwFuncTemp,
|
||||
"popSample": False,
|
||||
"popMinimal": False,
|
||||
"popCustom": True,
|
||||
"addRoots": [
|
||||
nwItemClass.PLOT,
|
||||
nwItemClass.CHARACTER,
|
||||
nwItemClass.WORLD,
|
||||
nwItemClass.TIMELINE,
|
||||
nwItemClass.OBJECT,
|
||||
nwItemClass.ENTITY,
|
||||
],
|
||||
"numChapters": 0,
|
||||
"numScenes": 6,
|
||||
"chFolders": True,
|
||||
}
|
||||
theProject = NWProject(nwDummy)
|
||||
theProject.projTree.setSeed(42)
|
||||
|
||||
assert theProject.newProject(projData)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewSampleA(nwFuncTemp, nwConf, nwDummy, nwTemp):
|
||||
"""Check that we can create a new project can be created from the
|
||||
provided sample project via a zip file.
|
||||
"""
|
||||
projData = {
|
||||
"projName": "Test Sample",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": nwFuncTemp,
|
||||
"popSample": True,
|
||||
"popMinimal": False,
|
||||
"popCustom": False,
|
||||
}
|
||||
theProject = NWProject(nwDummy)
|
||||
theProject.projTree.setSeed(42)
|
||||
theProject.mainConf = nwConf
|
||||
|
||||
# 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"))
|
||||
dstSample = os.path.join(nwTemp, "sample.zip")
|
||||
nwConf.assetPath = nwTemp
|
||||
|
||||
# Create and open a defective zip file
|
||||
with open(dstSample, mode="w+") as outFile:
|
||||
outFile.write("foo")
|
||||
|
||||
assert not theProject.newProject(projData)
|
||||
os.unlink(dstSample)
|
||||
|
||||
# Create a real zip file, and unpack it
|
||||
with ZipFile(dstSample, "w") as zipObj:
|
||||
zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx")
|
||||
for docFile in os.listdir(os.path.join(srcSample, "content")):
|
||||
srcDoc = os.path.join(srcSample, "content", docFile)
|
||||
zipObj.write(srcDoc, "content/"+docFile)
|
||||
|
||||
assert theProject.newProject(projData)
|
||||
assert theProject.openProject(nwFuncTemp)
|
||||
assert theProject.projName == "Sample Project"
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
os.unlink(dstSample)
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, nwDummy, nwTemp):
|
||||
"""Check that we can create a new project can be created from the
|
||||
provided sample project folder.
|
||||
"""
|
||||
projData = {
|
||||
"projName": "Test Sample",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": nwFuncTemp,
|
||||
"popSample": True,
|
||||
"popMinimal": False,
|
||||
"popCustom": False,
|
||||
}
|
||||
theProject = NWProject(nwDummy)
|
||||
theProject.projTree.setSeed(42)
|
||||
theProject.mainConf = nwConf
|
||||
|
||||
# Make sure we do not pick up the nw/assets/sample.zip file
|
||||
nwConf.assetPath = nwTemp
|
||||
|
||||
# Set a fake project file name
|
||||
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx")
|
||||
assert not theProject.newProject(projData)
|
||||
|
||||
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx")
|
||||
assert theProject.newProject(projData)
|
||||
assert theProject.openProject(nwFuncTemp)
|
||||
assert theProject.projName == "Sample Project"
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
|
||||
# Misdirect the appRoot path so neither is possible
|
||||
nwConf.appRoot = nwTemp
|
||||
assert not theProject.newProject(projData)
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectMethods(monkeypatch, nwMinimal, nwDummy):
|
||||
"""Test other project class methods and functions.
|
||||
"""
|
||||
theProject = NWProject(nwDummy)
|
||||
theProject.projTree.setSeed(42)
|
||||
assert theProject.openProject(nwMinimal)
|
||||
assert theProject.projPath == nwMinimal
|
||||
|
||||
# Setting project path
|
||||
assert theProject.setProjectPath(None)
|
||||
assert theProject.projPath is None
|
||||
assert theProject.setProjectPath("")
|
||||
assert theProject.projPath is None
|
||||
assert theProject.setProjectPath("~")
|
||||
assert theProject.projPath == os.path.expanduser("~")
|
||||
|
||||
# Create a new folder and populate it
|
||||
projPath = os.path.join(nwMinimal, "dummy1")
|
||||
assert theProject.setProjectPath(projPath, newProject=True)
|
||||
|
||||
# Make os.mkdir fail
|
||||
def altMkdir(*args):
|
||||
raise Exception("Oops!")
|
||||
|
||||
monkeypatch.setattr("os.mkdir", altMkdir)
|
||||
projPath = os.path.join(nwMinimal, "dummy2")
|
||||
assert not theProject.setProjectPath(projPath, newProject=True)
|
||||
|
||||
# Project Name
|
||||
assert theProject.setProjectName(" A Name ")
|
||||
assert theProject.projName == "A Name"
|
||||
|
||||
# Project Title
|
||||
assert theProject.setBookTitle(" A Title ")
|
||||
assert theProject.bookTitle == "A Title"
|
||||
|
||||
# Project Authors
|
||||
assert not theProject.setBookAuthors([])
|
||||
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
|
||||
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
|
||||
|
||||
@pytest.mark.project
|
||||
def testDocMeta(nwDummy, nwLipsum):
|
||||
"""Check that the document meta data string is parsed correctly.
|
||||
"""
|
||||
theProject = NWProject(nwDummy)
|
||||
theProject.projTree.setSeed(42)
|
||||
assert theProject.openProject(nwLipsum)
|
||||
|
||||
aDoc = NWDoc(theProject, nwDummy)
|
||||
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(nwTemp, nwConf):
|
||||
wList = os.path.join(nwTemp, "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(nwTemp, nwConf):
|
||||
wList = os.path.join(nwTemp, "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(nwDummy, 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)
|
||||
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(nwDummy, 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)
|
||||
assert theProject.openProject(nwLipsum)
|
||||
assert theProject.projTree["636b6aa9b697b"] is None
|
||||
assert theProject.closeProject()
|
||||
|
||||
# First Item with Meta Data
|
||||
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
|
||||
with open(orphPath, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("%%~name:Mars\n")
|
||||
outFile.write("%%~path:5eaea4e8cdee8/636b6aa9b697b\n")
|
||||
outFile.write("%%~kind:WORLD/NOTE\n")
|
||||
outFile.write("%%~invalid\n")
|
||||
outFile.write("\n")
|
||||
|
||||
# Second Item without Meta Data
|
||||
orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd")
|
||||
with open(orphPath, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
|
||||
# Invalid File Name
|
||||
dummyPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt")
|
||||
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
|
||||
# Invalid File Name
|
||||
dummyPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd")
|
||||
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
|
||||
# Invalid File Name
|
||||
dummyPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd")
|
||||
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
|
||||
assert theProject.openProject(nwLipsum)
|
||||
assert theProject.projPath is not None
|
||||
assert theProject.projTree["636b6aa9b697bb"] is None
|
||||
assert theProject.projTree["abcdefghijklm"] is None
|
||||
|
||||
# First Item with Meta Data
|
||||
oItem = theProject.projTree["636b6aa9b697b"]
|
||||
assert oItem is not None
|
||||
assert oItem.itemName == "Mars"
|
||||
assert oItem.itemHandle == "636b6aa9b697b"
|
||||
assert oItem.itemParent is None
|
||||
assert oItem.itemClass == nwItemClass.WORLD
|
||||
assert oItem.itemType == nwItemType.FILE
|
||||
assert oItem.itemLayout == nwItemLayout.NOTE
|
||||
|
||||
# Second Item without Meta Data
|
||||
oItem = theProject.projTree["736b6aa9b697b"]
|
||||
assert oItem is not None
|
||||
assert oItem.itemName == "Orphaned File 1"
|
||||
assert oItem.itemHandle == "736b6aa9b697b"
|
||||
assert oItem.itemParent is None
|
||||
assert oItem.itemClass == nwItemClass.NO_CLASS
|
||||
assert oItem.itemType == nwItemType.FILE
|
||||
assert oItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||
|
||||
assert theProject.saveProject(nwLipsum)
|
||||
assert theProject.closeProject()
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectOldFormat(nwDummy, 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.mainConf.showGUI = False
|
||||
|
||||
# Create dummy files for known legacy files
|
||||
deleteFiles = [
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.0"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.1"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.2"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.3"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.4"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.5"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.6"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.7"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.8"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.9"),
|
||||
os.path.join(nwOldProj, "meta", "mainOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "exportOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "outlineOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "timelineOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "docMergeOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "sessionLogOptions.json"),
|
||||
]
|
||||
|
||||
# Add some files that shouldn't be there
|
||||
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.nwd"))
|
||||
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.txt"))
|
||||
|
||||
# Add some folders that shouldn't be there
|
||||
os.mkdir(os.path.join(nwOldProj, "stuff"))
|
||||
os.mkdir(os.path.join(nwOldProj, "data_1", "stuff"))
|
||||
|
||||
# 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")
|
||||
for aFile in deleteFiles:
|
||||
assert os.path.isfile(aFile)
|
||||
|
||||
# Open project and check that files that are not supposed to be
|
||||
# there have been removed
|
||||
assert theProject.openProject(nwOldProj)
|
||||
for aFile in deleteFiles:
|
||||
assert not os.path.isfile(aFile)
|
||||
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_1", "stuff"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_1"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_7"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_8"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_9"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_a"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_f"))
|
||||
|
||||
# Check stuff that has been moved
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "junk"))
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "junk", "stuff"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.txt"))
|
||||
|
||||
# Check that files we want to keep are in the right place
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "cache"))
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "content"))
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "meta"))
|
||||
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "f528d831f5b24.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "88124a4292d8b.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "91239bf2f8b69.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "19752e7f9d8af.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "a764d5acf5a21.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "9058ae29f0dfd.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "7ff63b8afc4cd.nwd"))
|
||||
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "meta", "tagsIndex.json"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionInfo.log"))
|
||||
|
||||
# Close the project
|
||||
theProject.closeProject()
|
||||
|
||||
# Check that new files have been created
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "meta", "guiOptions.json"))
|
||||
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(nwDummy, nwMinimal, nwTemp):
|
||||
"""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)
|
||||
assert theProject.openProject(nwMinimal)
|
||||
|
||||
# Test faulty settings
|
||||
# Invalid path
|
||||
theProject.mainConf.backupPath = None
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
|
||||
# Missing project name
|
||||
theProject.mainConf.backupPath = nwTemp
|
||||
theProject.projName = ""
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
|
||||
# Non-existent folder
|
||||
theProject.mainConf.backupPath = os.path.join(nwTemp, "nonexistent")
|
||||
theProject.projName = "Test Minimal"
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
|
||||
# Same folder as project (causes infinite loop in zipping)
|
||||
theProject.mainConf.backupPath = nwMinimal
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
|
||||
# Test correct settings
|
||||
theProject.mainConf.backupPath = nwTemp
|
||||
assert theProject.zipIt(doNotify=False)
|
||||
|
||||
theFiles = os.listdir(os.path.join(nwTemp, "Test Minimal"))
|
||||
assert len(theFiles) == 1
|
||||
|
||||
theZip = theFiles[0]
|
||||
assert theZip[:12] == "Backup from "
|
||||
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"))
|
||||
|
||||
# Check that the main project file was restored
|
||||
assert cmpFiles(
|
||||
os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(nwTemp, "extract", "nwProject.nwx")
|
||||
)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user