Merge pull request #2 from vkbo/unit_tests

Unit Tests
This commit is contained in:
Veronica K. Berglyd Olsen
2019-05-12 15:01:14 +02:00
committed by GitHub
14 changed files with 453 additions and 66 deletions
+11 -1
View File
@@ -1,2 +1,12 @@
# Python Temp
__pycache__ __pycache__
*.bak
# Sample Project
sample/**/cache
sample/**/wordlist.txt
sample/**/*.bak
# PyTest
tests/temp
.pytest_cache
pytestdebug.log
+5 -4
View File
@@ -64,8 +64,7 @@ logger = logging.getLogger(__name__)
CONFIG = Config() CONFIG = Config()
def main(sysArgs): def main(sysArgs):
""" """Parses command line, sets up logging, and launches main GUI.
Parses command line, sets up logging, and launches main GUI.
""" """
# Valid Input Options # Valid Input Options
@@ -149,10 +148,10 @@ def main(sysArgs):
showGUI = False showGUI = False
elif inOpt in ("-D","--debuggui"): elif inOpt in ("-D","--debuggui"):
debugLevel = logging.DEBUG debugLevel = logging.DEBUG
debugStr = "{name:>22}:{lineno:<4d} {levelname:8} {message:}" debugStr = "{name:>20}:{lineno:<4d} {levelname:8} {message:}"
debugGUI = True debugGUI = True
# Set GUI options # Set Config Options
CONFIG.showGUI = showGUI CONFIG.showGUI = showGUI
CONFIG.debugGUI = debugGUI CONFIG.debugGUI = debugGUI
@@ -179,6 +178,8 @@ def main(sysArgs):
logger.setLevel(debugLevel) logger.setLevel(debugLevel)
CONFIG.initConfig(confPath)
nwApp = QApplication([]) nwApp = QApplication([])
nwGUI = NovelWriter() nwGUI = NovelWriter()
exit(nwApp.exec_()) exit(nwApp.exec_())
+51 -29
View File
@@ -6,7 +6,7 @@
This class reads and store the main preferences of the application This class reads and store the main preferences of the application
File History: File History:
Created: 2018-0+-22 [0.0.1] Created: 2018-09-22 [0.0.1]
""" """
@@ -28,25 +28,18 @@ class Config:
def __init__(self): def __init__(self):
# Set Application Variables # Set Application Variables
self.appName = nw.__package__ self.appName = nw.__package__
self.appHandle = nw.__package__.lower() self.appHandle = nw.__package__.lower()
self.showGUI = True self.showGUI = True
self.debugGUI = False self.debugGUI = False
# Set Paths # Set Paths
self.confPath = user_config_dir(self.appHandle) self.confPath = None
self.confFile = self.appHandle+".conf" self.confFile = None
self.homePath = path.expanduser("~") self.homePath = None
self.appPath = path.dirname(__file__) self.appPath = None
self.guiPath = path.join(self.appPath,"gui") self.guiPath = None
self.themePath = path.join(self.appPath,"themes") self.themePath = None
self.recentList = [""]*10
# If config folder does not exist, make it.
# This assumes that the os config folder itself exists.
# TODO: This does not work on Windows
if not path.isdir(self.confPath):
mkdir(self.confPath)
# Set default values # Set default values
self.confChanged = False self.confChanged = False
@@ -81,13 +74,8 @@ class Config:
self.spellLanguage = "en_GB" self.spellLanguage = "en_GB"
# Check if config file exists # Path
if path.isfile(path.join(self.confPath,self.confFile)): self.recentList = [""]*10
self.loadConfig()
# Save a copy of the default config if no file exists
if not path.isfile(path.join(self.confPath,self.confFile)):
self.saveConfig()
return return
@@ -95,11 +83,41 @@ class Config:
# Actions # Actions
## ##
def initConfig(self, confPath=None):
if confPath is None:
self.confPath = user_config_dir(self.appHandle)
else:
logger.info("Setting config from alternative path: %s" % confPath)
self.confPath = confPath
self.confFile = self.appHandle+".conf"
self.homePath = path.expanduser("~")
self.appPath = path.dirname(__file__)
self.guiPath = path.join(self.appPath,"gui")
self.themePath = path.join(self.appPath,"themes")
# If config folder does not exist, make it.
# This assumes that the os config folder itself exists.
# TODO: This does not work on Windows
if not path.isdir(self.confPath):
mkdir(self.confPath)
# Check if config file exists
if path.isfile(path.join(self.confPath,self.confFile)):
# If it exists, load it
self.loadConfig()
else:
# If it does not exist, save a copy of the defaults
self.saveConfig()
return True
def loadConfig(self): def loadConfig(self):
logger.debug("Loading config file") logger.debug("Loading config file")
confParser = configparser.ConfigParser() confParser = configparser.ConfigParser()
confParser.readfp(open(path.join(self.confPath,self.confFile))) confParser.read_file(open(path.join(self.confPath,self.confFile)))
# Get options # Get options
@@ -214,10 +232,14 @@ class Config:
confParser.set(cnfSec,"recent%d" % i, str(self.recentList[i])) confParser.set(cnfSec,"recent%d" % i, str(self.recentList[i]))
# Write config file # Write config file
confParser.write(open(path.join(self.confPath,self.confFile),"w")) try:
self.confChanged = False confParser.write(open(path.join(self.confPath,self.confFile),"w"))
self.confChanged = False
except Exception as e:
logger.error("Could not save config file")
return False
return return True
def unpackList(self, inStr, listLen, listDefault, castTo=int): def unpackList(self, inStr, listLen, listDefault, castTo=int):
inData = inStr.split(",") inData = inStr.split(",")
+3 -2
View File
@@ -35,6 +35,7 @@ class GuiDocDetails(QFrame):
logger.debug("Initialising DocDetails ...") logger.debug("Initialising DocDetails ...")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.debugGUI = self.mainConf.debugGUI self.debugGUI = self.mainConf.debugGUI
self.theParent = theParent
self.theProject = theProject self.theProject = theProject
self.mainBox = QGridLayout(self) self.mainBox = QGridLayout(self)
@@ -70,11 +71,11 @@ class GuiDocDetails(QFrame):
colTwo = [""]*4 colTwo = [""]*4
else: else:
itemStatus = nwItem.itemStatus itemStatus = nwItem.itemStatus
if itemStatus < 0 or itemStatus >= len(self.theProject.statusLabels): if itemStatus < 0 or itemStatus >= len(self.theParent.statusLabels):
itemStatus = 0 itemStatus = 0
colTwo = [ colTwo = [
nwItem.itemName, nwItem.itemName,
self.theProject.statusLabels[itemStatus], self.theParent.statusLabels[itemStatus],
nwLabels.CLASS_NAME[nwItem.itemClass], nwLabels.CLASS_NAME[nwItem.itemClass],
nwLabels.LAYOUT_NAME[nwItem.itemLayout], nwLabels.LAYOUT_NAME[nwItem.itemLayout],
] ]
+2 -2
View File
@@ -252,12 +252,12 @@ class GuiDocTree(QTreeWidget):
if nwItem.itemType == nwItemType.FILE: if nwItem.itemType == nwItemType.FILE:
tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout] tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
nStatus = nwItem.itemStatus nStatus = nwItem.itemStatus
if nStatus < 0 or nStatus >= len(self.theProject.statusIcons): if nStatus < 0 or nStatus >= len(self.theParent.statusIcons):
nStatus = 0 nStatus = 0
trItem.setText(self.C_NAME,tName) trItem.setText(self.C_NAME,tName)
trItem.setText(self.C_FLAGS,tStatus) trItem.setText(self.C_FLAGS,tStatus)
trItem.setIcon(self.C_FLAGS,self.theProject.statusIcons[nStatus]) trItem.setIcon(self.C_FLAGS,self.theParent.statusIcons[nStatus])
return return
+26 -3
View File
@@ -15,7 +15,7 @@ import nw
from os import path from os import path
from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QStackedWidget, QShortcut, QMessageBox from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QStackedWidget, QShortcut, QMessageBox
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon, QPixmap, QColor
from PyQt5.QtCore import Qt, QTimer from PyQt5.QtCore import Qt, QTimer
from nw.gui.doctree import GuiDocTree from nw.gui.doctree import GuiDocTree
@@ -57,6 +57,10 @@ class GuiMain(QMainWindow):
self.mainMenu = GuiMainMenu(self, self.theProject) self.mainMenu = GuiMainMenu(self, self.theProject)
self.statusBar = GuiMainStatus(self) self.statusBar = GuiMainStatus(self)
# Minor Gui Elements
self.statusIcons = []
self.statusLabels = []
# Assemble Main Window # Assemble Main Window
self.stackPane = QStackedWidget() self.stackPane = QStackedWidget()
self.stackNone = self.stackPane.addWidget(QWidget()) self.stackNone = self.stackPane.addWidget(QWidget())
@@ -82,7 +86,7 @@ class GuiMain(QMainWindow):
self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.treeView.buildTree() self.treeView.buildTree()
QShortcut(Qt.Key_Return, self.treeView, context=Qt.WidgetShortcut, activated=self._treeKeyPressReturn) self._makeStatusIcons()
# Set Main Window Elements # Set Main Window Elements
self.setMenuBar(self.mainMenu) self.setMenuBar(self.mainMenu)
@@ -106,7 +110,15 @@ class GuiMain(QMainWindow):
self.asDocTimer.timeout.connect(self._autoSaveDocument) self.asDocTimer.timeout.connect(self._autoSaveDocument)
self.asDocTimer.start() self.asDocTimer.start()
self.show() # Keyboard Shortcuts
QShortcut(Qt.Key_Return, self.treeView, context=Qt.WidgetShortcut, activated=self._treeKeyPressReturn)
# Forward Functions
self.setStatus = self.statusBar.setStatus
self.setProjectStatus = self.statusBar.setProjectStatus
if self.mainConf.showGUI:
self.show()
logger.debug("GUI initialisation complete") logger.debug("GUI initialisation complete")
@@ -162,6 +174,7 @@ class GuiMain(QMainWindow):
self.treeView.buildTree() self.treeView.buildTree()
self.mainMenu.updateRecentProjects() self.mainMenu.updateRecentProjects()
self._setWindowTitle(self.theProject.projName) self._setWindowTitle(self.theProject.projName)
self._makeStatusIcons()
self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt")) self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt"))
return True return True
@@ -341,6 +354,16 @@ class GuiMain(QMainWindow):
return False return False
return True return True
def _makeStatusIcons(self):
self.statusIcons = []
self.statusLabels = []
for sLabel, sR, sG, sB in self.theProject.statusCols:
theIcon = QPixmap(32,32)
theIcon.fill(QColor(sR,sG,sB))
self.statusIcons.append(QIcon(theIcon))
self.statusLabels.append(sLabel)
return
## ##
# Events # Events
## ##
+17 -25
View File
@@ -19,8 +19,6 @@ from hashlib import sha256
from datetime import datetime from datetime import datetime
from time import time from time import time
from PyQt5.QtGui import QIcon, QPixmap, QColor
from nw.enum import nwItemType, nwItemClass, nwItemLayout from nw.enum import nwItemType, nwItemClass, nwItemLayout
from nw.project.item import NWItem from nw.project.item import NWItem
@@ -31,10 +29,13 @@ class NWProject():
def __init__(self, theParent): def __init__(self, theParent):
# Internal # Internal
self.mainConf = nw.CONFIG
self.theParent = theParent self.theParent = theParent
self.mainConf = self.theParent.mainConf
self.projChanged = None self.projChanged = None
# Debug
self.handleSeed = None
# Project Settings # Project Settings
self.projTree = None self.projTree = None
self.treeOrder = None self.treeOrder = None
@@ -48,8 +49,6 @@ class NWProject():
self.bookTitle = None self.bookTitle = None
self.bookAuthors = None self.bookAuthors = None
self.statusCols = None self.statusCols = None
self.statusIcons = None
self.statusLabels = None
self.clearProject() self.clearProject()
@@ -60,6 +59,9 @@ class NWProject():
## ##
def newRoot(self, rootName, rootClass): def newRoot(self, rootName, rootClass):
if not self.checkRootUnique(rootClass):
self.theParent.makeAlert("Duplicate root item detected!",2)
return None
newItem = NWItem() newItem = NWItem()
newItem.setName(rootName) newItem.setName(rootName)
newItem.setType(nwItemType.ROOT) newItem.setType(nwItemType.ROOT)
@@ -110,7 +112,7 @@ class NWProject():
hChapt = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel) hChapt = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel)
hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapt) hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapt)
return return True
def clearProject(self): def clearProject(self):
@@ -128,16 +130,12 @@ class NWProject():
self.projName = "" self.projName = ""
self.bookTitle = "" self.bookTitle = ""
self.bookAuthors = [] self.bookAuthors = []
self.statusCols = [ self.statusCols = [
("New", 100,100,100), ("New", 100,100,100),
("Note", 200, 50, 0), ("Note", 200, 50, 0),
("Draft", 200,150, 0), ("Draft", 200,150, 0),
("Finished", 50,200, 0), ("Finished", 50,200, 0),
] ]
self.statusIcons = []
self.statusLabels = []
self._makeStatusIcons()
return return
@@ -205,9 +203,8 @@ class NWProject():
nwItem.setFromTag(xValue.tag,xValue.text) nwItem.setFromTag(xValue.tag,xValue.text)
self._appendItem(tHandle,pHandle,nwItem) self._appendItem(tHandle,pHandle,nwItem)
self._makeStatusIcons()
self.mainConf.setRecent(self.projPath) self.mainConf.setRecent(self.projPath)
self.theParent.statusBar.setStatus("Opened Project: %s" % self.projName) self.theParent.setStatus("Opened Project: %s" % self.projName)
self._scanProjectFolder() self._scanProjectFolder()
self.setProjectChanged(False) self.setProjectChanged(False)
@@ -267,7 +264,7 @@ class NWProject():
return False return False
self.mainConf.setRecent(self.projPath) self.mainConf.setRecent(self.projPath)
self.theParent.statusBar.setStatus("Saved Project: %s" % self.projName) self.theParent.setStatus("Saved Project: %s" % self.projName)
self.setProjectChanged(False) self.setProjectChanged(False)
return True return True
@@ -310,7 +307,7 @@ class NWProject():
def setProjectChanged(self, bValue): def setProjectChanged(self, bValue):
self.projChanged = bValue self.projChanged = bValue
self.theParent.statusBar.setProjectStatus(self.projChanged) self.theParent.setProjectStatus(self.projChanged)
return return
## ##
@@ -430,18 +427,13 @@ class NWProject():
return return
def _makeStatusIcons(self):
self.statusIcons = []
self.statusLabels = []
for sLabel, sR, sG, sB in self.statusCols:
theIcon = QPixmap(32,32)
theIcon.fill(QColor(sR,sG,sB))
self.statusIcons.append(QIcon(theIcon))
self.statusLabels.append(sLabel)
return
def _makeHandle(self, addSeed=""): def _makeHandle(self, addSeed=""):
newSeed = str(time()) + addSeed if self.handleSeed is None:
newSeed = str(time()) + addSeed
else:
# This is used for debugging
newSeed = str(self.handleSeed)
self.handleSeed += 1
logger.verbose("Generating handle with seed '%s'" % newSeed) logger.verbose("Generating handle with seed '%s'" % newSeed)
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13] itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
if itemHandle in self.projTree.keys(): if itemHandle in self.projTree.keys():
+31
View File
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""novelWriter Test Dummy GUI Classes
"""
class DummyMain():
def __init__(self):
self.mainConf = None
return
def makeAlert(self, theMessage, theLevel):
if theLevel == 1:
lvlMsg = "WARNING: "
elif theLevel == 2:
lvlMsg = "ERROR: "
else:
lvlMsg = ""
if isinstance(theMessage, list):
for msgLine in logMsg:
print(lvlMsg+msgLine)
else:
print(lvlMsg+theMessage)
return
def setStatus(self, theMessage):
return
def setProjectStatus(self, isChanged):
return
# END Class GuiMain
+41
View File
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""novelWriter Test Tools
"""
from os import path, mkdir
def ensureDir(theDir):
if not path.isdir(theDir):
mkdir(theDir)
return
def cmpFiles(fileOne, fileTwo, ignoreLines=[]):
foOne = open(fileOne,mode="r")
foTwo = open(fileTwo,mode="r")
txtOne = foOne.readlines()
txtTwo = foTwo.readlines()
if len(txtOne) != len(txtTwo):
print("Files are not the same length")
return False
diffFound = False
for n in range(len(txtOne)):
lnOne = txtOne[n].strip()
lnTwo = txtTwo[n].strip()
if n+1 in ignoreLines:
print("Ignoring line %d" % (n+1))
continue
if lnOne != lnTwo:
print("Diff on line %d:" % (n+1))
print(" << '%s'" % lnOne)
print(" >> '%s'" % lnTwo)
diffFound = True
foOne.close()
foTwo.close()
return not diffFound
+55
View File
@@ -0,0 +1,55 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.0" fileVersion="1.0" timeStamp="2019-05-12 14:21:08">
<project>
<name></name>
<title></title>
</project>
<content count="6">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="44cb730c42048" order="None" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="None" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="811786ad1ae74" order="None" parent="None">
<name>World</name>
<type>ROOT</type>
<class>WORLD</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="None" parent="25fc0e7096fc6">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>0</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
</item>
</content>
</novelWriterXML>
+38
View File
@@ -0,0 +1,38 @@
[Main]
timestamp = 2019-05-12 12:04:16
[Sizes]
geometry = 1100, 650
treecols = 120, 30, 50
mainpane = 300, 800
[Project]
autosaveproject = 60
autosavedoc = 30
[Editor]
fixedwidth = True
width = 600
margins = 40, 40
textsize = 13
justify = True
autoselect = True
autoreplace = True
repsquotes = True
repdquotes = True
repdash = True
repdots = True
spellcheck = en_GB
[Path]
recent0 =
recent1 =
recent2 =
recent3 =
recent4 =
recent5 =
recent6 =
recent7 =
recent8 =
recent9 =
+83
View File
@@ -0,0 +1,83 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.0" fileVersion="1.0" timeStamp="2019-05-12 14:40:19">
<project>
<name></name>
<title></title>
</project>
<content count="10">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="44cb730c42048" order="None" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="None" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="811786ad1ae74" order="None" parent="None">
<name>World</name>
<type>ROOT</type>
<class>WORLD</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="None" parent="25fc0e7096fc6">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>0</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
</item>
<item handle="39fa9ec190eee" order="None" parent="None">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="d029fa3a95e17" order="None" parent="None">
<name>Object</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="81b8a03f97e87" order="None" parent="None">
<name>Custom1</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>0</status>
<expanded>False</expanded>
</item>
<item handle="da4ea2a5506f2" order="None" parent="None">
<name>Custom2</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>0</status>
<expanded>False</expanded>
</item>
</content>
</novelWriterXML>
+29
View File
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""novelWriter Config Class Tester
"""
import nw
from nwtools import *
from os import path, unlink
from nw.config import Config
theConf = Config()
testDir = path.dirname(__file__)
testTemp = path.join(testDir,"temp")
testRef = path.join(testDir,"reference")
tmpConf = path.join(testTemp,"novelwriter.conf")
refConf = path.join(testRef, "novelwriter.conf")
ensureDir(testTemp)
# Clean out old stuff
if path.isfile(tmpConf):
unlink(tmpConf)
def testConfigInit():
assert theConf.initConfig(testTemp)
assert cmpFiles(tmpConf, refConf, [2])
def testConfigSave():
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
+61
View File
@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""novelWriter Project Class Tester
"""
import nw
import types
from os import path, unlink
from nwtools import *
from nwdummy import DummyMain
from nw.config import Config
from nw.project.project import NWProject
from nw.project.item import NWItem
from nw.enum import nwItemClass
theConf = Config()
theMain = DummyMain()
theMain.mainConf = theConf
testDir = path.dirname(__file__)
testTemp = path.join(testDir,"temp")
testRef = path.join(testDir,"reference")
testProj = path.join(testTemp,"proj")
ensureDir(testTemp)
ensureDir(testProj)
theConf.initConfig(testRef)
theProject = NWProject(theMain)
theProject.handleSeed = 42
projFile = path.join(testProj,"nwProject.nwx")
def testProjectNew():
assert theProject.newProject()
assert theProject.setProjectPath(testProj)
assert theProject.saveProject()
assert cmpFiles(projFile, path.join(testRef,"new_nwProject.nwx"), [2])
def testProjectOpen():
assert theProject.openProject(projFile)
def testProjectSave():
assert theProject.saveProject()
assert cmpFiles(projFile, path.join(testRef,"new_nwProject.nwx"), [2])
assert not theProject.projChanged
def testProjectNewRoot():
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 cmpFiles(projFile, path.join(testRef,"roots_nwProject.nwx"), [2])
assert not theProject.projChanged