Merge pull request #512 from vkbo/refactor_tests

Rewrite Core Tests
This commit is contained in:
Veronica K. Berglyd Olsen
2020-12-06 00:17:27 +01:00
committed by GitHub
43 changed files with 4107 additions and 1368 deletions
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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/>")
+8 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,6 +1,5 @@
[pytest]
markers =
project: Project classes tests
error: Test various error handling scenarios
core: Core functionality tests
gui: Qt5 GUI tests
+74
View File
@@ -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
View File
@@ -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
+73
View File
@@ -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
-77
View File
@@ -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"
]
]
-17
View File
@@ -1,17 +0,0 @@
[
[
"content/8c659a11cd429.nwd",
"NOVEL",
"New Scene"
],
[
"content/a35baf2e93843.nwd",
"NOVEL",
"Title Page"
],
[
"content/f5ab3e30151e1.nwd",
"NOVEL",
"New Chapter"
]
]
-39
View File
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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
+145
View File
@@ -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
+153 -40
View File
@@ -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
+133
View File
@@ -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
+170
View File
@@ -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
+126
View File
@@ -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
+373
View File
@@ -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'>&nbsp;</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> & shortdash, long—dash …\n"
theHtml.theText = docText
theHtml.doAutoReplace()
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == (
"<p>Text with &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;</p>\n"
)
# Revert on MD
assert theHtml.theMarkdown == (
"Text with &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;\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 &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;\n\n"
)
theHtml.doPostProcessing()
assert theHtml.theMarkdown == (
"Text with &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;\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
+644
View File
@@ -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
+433
View File
@@ -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
View File
@@ -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()
+48 -48
View File
@@ -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()
-679
View File
@@ -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")
)
+12
View File
@@ -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)