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__
*.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()
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
@@ -149,10 +148,10 @@ def main(sysArgs):
showGUI = False
elif inOpt in ("-D","--debuggui"):
debugLevel = logging.DEBUG
debugStr = "{name:>22}:{lineno:<4d} {levelname:8} {message:}"
debugStr = "{name:>20}:{lineno:<4d} {levelname:8} {message:}"
debugGUI = True
# Set GUI options
# Set Config Options
CONFIG.showGUI = showGUI
CONFIG.debugGUI = debugGUI
@@ -179,6 +178,8 @@ def main(sysArgs):
logger.setLevel(debugLevel)
CONFIG.initConfig(confPath)
nwApp = QApplication([])
nwGUI = NovelWriter()
exit(nwApp.exec_())
+51 -29
View File
@@ -6,7 +6,7 @@
This class reads and store the main preferences of the application
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):
# Set Application Variables
self.appName = nw.__package__
self.appHandle = nw.__package__.lower()
self.showGUI = True
self.debugGUI = False
self.appName = nw.__package__
self.appHandle = nw.__package__.lower()
self.showGUI = True
self.debugGUI = False
# Set Paths
self.confPath = user_config_dir(self.appHandle)
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")
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)
self.confPath = None
self.confFile = None
self.homePath = None
self.appPath = None
self.guiPath = None
self.themePath = None
# Set default values
self.confChanged = False
@@ -81,13 +74,8 @@ class Config:
self.spellLanguage = "en_GB"
# Check if config file exists
if path.isfile(path.join(self.confPath,self.confFile)):
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()
# Path
self.recentList = [""]*10
return
@@ -95,11 +83,41 @@ class Config:
# 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):
logger.debug("Loading config file")
confParser = configparser.ConfigParser()
confParser.readfp(open(path.join(self.confPath,self.confFile)))
confParser.read_file(open(path.join(self.confPath,self.confFile)))
# Get options
@@ -214,10 +232,14 @@ class Config:
confParser.set(cnfSec,"recent%d" % i, str(self.recentList[i]))
# Write config file
confParser.write(open(path.join(self.confPath,self.confFile),"w"))
self.confChanged = False
try:
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):
inData = inStr.split(",")
+3 -2
View File
@@ -35,6 +35,7 @@ class GuiDocDetails(QFrame):
logger.debug("Initialising DocDetails ...")
self.mainConf = nw.CONFIG
self.debugGUI = self.mainConf.debugGUI
self.theParent = theParent
self.theProject = theProject
self.mainBox = QGridLayout(self)
@@ -70,11 +71,11 @@ class GuiDocDetails(QFrame):
colTwo = [""]*4
else:
itemStatus = nwItem.itemStatus
if itemStatus < 0 or itemStatus >= len(self.theProject.statusLabels):
if itemStatus < 0 or itemStatus >= len(self.theParent.statusLabels):
itemStatus = 0
colTwo = [
nwItem.itemName,
self.theProject.statusLabels[itemStatus],
self.theParent.statusLabels[itemStatus],
nwLabels.CLASS_NAME[nwItem.itemClass],
nwLabels.LAYOUT_NAME[nwItem.itemLayout],
]
+2 -2
View File
@@ -252,12 +252,12 @@ class GuiDocTree(QTreeWidget):
if nwItem.itemType == nwItemType.FILE:
tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
nStatus = nwItem.itemStatus
if nStatus < 0 or nStatus >= len(self.theProject.statusIcons):
if nStatus < 0 or nStatus >= len(self.theParent.statusIcons):
nStatus = 0
trItem.setText(self.C_NAME,tName)
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
+26 -3
View File
@@ -15,7 +15,7 @@ import nw
from os import path
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 nw.gui.doctree import GuiDocTree
@@ -57,6 +57,10 @@ class GuiMain(QMainWindow):
self.mainMenu = GuiMainMenu(self, self.theProject)
self.statusBar = GuiMainStatus(self)
# Minor Gui Elements
self.statusIcons = []
self.statusLabels = []
# Assemble Main Window
self.stackPane = QStackedWidget()
self.stackNone = self.stackPane.addWidget(QWidget())
@@ -82,7 +86,7 @@ class GuiMain(QMainWindow):
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.treeView.buildTree()
QShortcut(Qt.Key_Return, self.treeView, context=Qt.WidgetShortcut, activated=self._treeKeyPressReturn)
self._makeStatusIcons()
# Set Main Window Elements
self.setMenuBar(self.mainMenu)
@@ -106,7 +110,15 @@ class GuiMain(QMainWindow):
self.asDocTimer.timeout.connect(self._autoSaveDocument)
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")
@@ -162,6 +174,7 @@ class GuiMain(QMainWindow):
self.treeView.buildTree()
self.mainMenu.updateRecentProjects()
self._setWindowTitle(self.theProject.projName)
self._makeStatusIcons()
self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt"))
return True
@@ -341,6 +354,16 @@ class GuiMain(QMainWindow):
return False
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
##
+17 -25
View File
@@ -19,8 +19,6 @@ from hashlib import sha256
from datetime import datetime
from time import time
from PyQt5.QtGui import QIcon, QPixmap, QColor
from nw.enum import nwItemType, nwItemClass, nwItemLayout
from nw.project.item import NWItem
@@ -31,10 +29,13 @@ class NWProject():
def __init__(self, theParent):
# Internal
self.mainConf = nw.CONFIG
self.theParent = theParent
self.mainConf = self.theParent.mainConf
self.projChanged = None
# Debug
self.handleSeed = None
# Project Settings
self.projTree = None
self.treeOrder = None
@@ -48,8 +49,6 @@ class NWProject():
self.bookTitle = None
self.bookAuthors = None
self.statusCols = None
self.statusIcons = None
self.statusLabels = None
self.clearProject()
@@ -60,6 +59,9 @@ class NWProject():
##
def newRoot(self, rootName, rootClass):
if not self.checkRootUnique(rootClass):
self.theParent.makeAlert("Duplicate root item detected!",2)
return None
newItem = NWItem()
newItem.setName(rootName)
newItem.setType(nwItemType.ROOT)
@@ -110,7 +112,7 @@ class NWProject():
hChapt = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel)
hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapt)
return
return True
def clearProject(self):
@@ -128,16 +130,12 @@ class NWProject():
self.projName = ""
self.bookTitle = ""
self.bookAuthors = []
self.statusCols = [
("New", 100,100,100),
("Note", 200, 50, 0),
("Draft", 200,150, 0),
("Finished", 50,200, 0),
]
self.statusIcons = []
self.statusLabels = []
self._makeStatusIcons()
return
@@ -205,9 +203,8 @@ class NWProject():
nwItem.setFromTag(xValue.tag,xValue.text)
self._appendItem(tHandle,pHandle,nwItem)
self._makeStatusIcons()
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.setProjectChanged(False)
@@ -267,7 +264,7 @@ class NWProject():
return False
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)
return True
@@ -310,7 +307,7 @@ class NWProject():
def setProjectChanged(self, bValue):
self.projChanged = bValue
self.theParent.statusBar.setProjectStatus(self.projChanged)
self.theParent.setProjectStatus(self.projChanged)
return
##
@@ -430,18 +427,13 @@ class NWProject():
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=""):
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)
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
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