Updated and new tests for NWProject class

This commit is contained in:
Veronica K. B. Olsen
2020-12-05 21:06:57 +01:00
parent c17d4de0a5
commit c353e9ffb1
10 changed files with 1101 additions and 412 deletions
+27 -24
View File
@@ -1125,13 +1125,11 @@ class NWProject():
sentItems = [] sentItems = []
iterItems = self.projTree.handles() iterItems = self.projTree.handles()
n = 0 n = 0
nMax = len(iterItems) nMax = min(len(iterItems), 10000)
while n < nMax: while n < nMax:
tHandle = iterItems[n] tHandle = iterItems[n]
tItem = self.projTree[tHandle] tItem = self.projTree[tHandle]
n += 1 n += 1
if n > 10000:
return # Just in case
if tItem is None: if tItem is None:
# Technically a bug since treeOrder is built from the # Technically a bug since treeOrder is built from the
# same data as projTree # same data as projTree
@@ -1147,10 +1145,11 @@ class NWProject():
yield tItem yield tItem
elif tItem.itemParent in iterItems: elif tItem.itemParent in iterItems:
# Item's parent exists, but hasn't been sent yet, so add # 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) logger.warning("Item %s found before its parent" % tHandle)
iterItems.append(tHandle) iterItems.append(tHandle)
nMax = len(iterItems) nMax = min(len(iterItems), 10000)
else: else:
# Item is orphaned # Item is orphaned
logger.error("Item %s has no parent in current tree" % tHandle) logger.error("Item %s has no parent in current tree" % tHandle)
@@ -1189,13 +1188,12 @@ class NWProject():
if not os.path.isfile(lockFile): if not os.path.isfile(lockFile):
return [] return []
theLines = []
try: try:
with open(lockFile, mode="r", encoding="utf8") as inFile: with open(lockFile, mode="r", encoding="utf8") as inFile:
theData = inFile.read() theData = inFile.read()
theLines = theData.splitlines() theLines = theData.splitlines()
if len(theLines) == 4: if len(theLines) != 4:
return theLines
else:
return ["ERROR"] return ["ERROR"]
except Exception as e: except Exception as e:
@@ -1203,7 +1201,7 @@ class NWProject():
logger.error(str(e)) logger.error(str(e))
return ["ERROR"] return ["ERROR"]
return ["ERROR"] return theLines
def _writeLockFile(self): def _writeLockFile(self):
"""Writes a lock file to the project folder. """Writes a lock file to the project folder.
@@ -1236,13 +1234,12 @@ class NWProject():
if os.path.isfile(lockFile): if os.path.isfile(lockFile):
try: try:
os.unlink(lockFile) os.unlink(lockFile)
return True
except Exception as e: except Exception as e:
logger.error("Failed to remove project lockfile") logger.error("Failed to remove project lockfile")
logger.error(str(e)) logger.error(str(e))
return False return False
return None return True
def _checkFolder(self, thePath): def _checkFolder(self, thePath):
"""Check if a folder exists, and if it doesn't, create it. """Check if a folder exists, and if it doesn't, create it.
@@ -1356,21 +1353,27 @@ class NWProject():
sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS) sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS)
isFile = os.path.isfile(sessionFile) isFile = os.path.isfile(sessionFile)
with open(sessionFile, mode="a+", encoding="utf8") as outFile: try:
if not isFile: with open(sessionFile, mode="a+", encoding="utf8") as outFile:
# It's a new file, so add a header if not isFile:
if self.lastWCount > 0: # It's a new file, so add a header
outFile.write("# Offset %d\n" % self.lastWCount) if self.lastWCount > 0:
outFile.write("# %-17s %-19s %8s %8s\n" % ( outFile.write("# Offset %d\n" % self.lastWCount)
"Start Time", "End Time", "Novel", "Notes" 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" % ( except Exception as e:
formatTimeStamp(self.projOpened), logger.error("Failed to write session stats file")
formatTimeStamp(time()), logger.error(str(e))
self.novelWCount, return False
self.notesWCount,
))
return True return True
+1
View File
@@ -65,6 +65,7 @@ The commands for the respective test categories are listed below.
| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | | Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` |
| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | | Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` |
| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` | | 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 | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` | | Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` | | Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
+7 -2
View File
@@ -6,12 +6,15 @@ import sys
import pytest import pytest
import shutil import shutil
import os import os
import time
from dummy import DummyMain from dummy import DummyMain
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
os.environ["TZ"] = "UTC"
time.tzset()
from nw.config import Config # noqa: E402 from nw.config import Config # noqa: E402
@@ -69,7 +72,7 @@ def fncDir(tmpDir):
# novelWriter Objects # novelWriter Objects
## ##
@pytest.fixture(scope="session") @pytest.fixture(scope="function")
def tmpConf(tmpDir): def tmpConf(tmpDir):
"""Create a temporary novelWriter configuration object. """Create a temporary novelWriter configuration object.
""" """
@@ -78,7 +81,7 @@ def tmpConf(tmpDir):
theConf.setLastPath("") theConf.setLastPath("")
return theConf return theConf
@pytest.fixture(scope="session") @pytest.fixture(scope="function")
def dummyGUI(tmpConf): def dummyGUI(tmpConf):
"""Create a dummy instance of novelWriter's main GUI class. """Create a dummy instance of novelWriter's main GUI class.
""" """
@@ -168,6 +171,8 @@ def yesToAll(monkeypatch):
monkeypatch.setattr( monkeypatch.setattr(
QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes
) )
yield
monkeypatch.undo()
return return
# =============================================================================================== # # =============================================================================================== #
+20
View File
@@ -14,12 +14,22 @@ class DummyMain():
self.theIndex = None self.theIndex = None
self.theProject = None self.theProject = None
self.statusBar = StatusBar() self.statusBar = StatusBar()
# Test Variables
self.askResponse = True
self.lastAlert = ""
return return
def makeAlert(self, theMessage, theLevel): def makeAlert(self, theMessage, theLevel):
print("%s: %s" % (str(theLevel), theMessage)) print("%s: %s" % (str(theLevel), theMessage))
self.lastAlert = str(theMessage)
return return
def askQuestion(self, theTitle, theQustion):
print("Question: %s" % theQustion)
return self.askResponse
def setStatus(self, theMessage): def setStatus(self, theMessage):
return return
@@ -32,6 +42,16 @@ class DummyMain():
def rebuildIndex(self): def rebuildIndex(self):
return return
# Test Functions
def undo(self):
self.askResponse = True
return
def clear(self):
self.lastAlert = ""
return
# END Class GuiMain # END Class GuiMain
class StatusBar(): class StatusBar():
+159 -30
View File
@@ -1,9 +1,11 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43"> <novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43">
<project> <project>
<name>New Project</name> <name>Test Custom</name>
<title></title> <title>Test Novel</title>
<saveCount>2</saveCount> <author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount> <autoCount>1</autoCount>
<editTime>0</editTime> <editTime>0</editTime>
</project> </project>
@@ -38,7 +40,7 @@
<entry blue="0" green="200" red="50">Main</entry> <entry blue="0" green="200" red="50">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="12"> <content count="23">
<item handle="73475cb40a568" order="0" parent="None"> <item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name> <name>Novel</name>
<type>ROOT</type> <type>ROOT</type>
@@ -61,13 +63,34 @@
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="811786ad1ae74" order="0" parent="None"> <item handle="811786ad1ae74" order="0" parent="None">
<name>World</name> <name>Locations</name>
<type>ROOT</type> <type>ROOT</type>
<class>WORLD</class> <class>WORLD</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568"> <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="0" parent="None">
<name>Objects</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<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="0" parent="73475cb40a568">
<name>Title Page</name> <name>Title Page</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
@@ -79,15 +102,15 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="31489056e0916" order="0" parent="73475cb40a568"> <item handle="1a6562590ef19" order="0" parent="73475cb40a568">
<name>New Chapter</name> <name>Chapter 1</name>
<type>FOLDER</type> <type>FOLDER</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="98010bd9270f9" order="0" parent="31489056e0916"> <item handle="031b4af5197ec" order="0" parent="1a6562590ef19">
<name>New Chapter</name> <name>Chapter 1</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -98,8 +121,8 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916"> <item handle="41cfc0d1f2d12" order="0" parent="1a6562590ef19">
<name>New Scene</name> <name>Scene 1.1</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -110,33 +133,139 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="1a6562590ef19" order="0" parent="None"> <item handle="2858dcd1057d3" order="0" parent="1a6562590ef19">
<name>Timeline</name> <name>Scene 1.2</name>
<type>ROOT</type> <type>FILE</type>
<class>TIMELINE</class> <class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2fca346db6561" order="0" parent="1a6562590ef19">
<name>Scene 1.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="02d20bbd7e394" order="0" parent="73475cb40a568">
<name>Chapter 2</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="031b4af5197ec" order="0" parent="None"> <item handle="7688b6ef52555" order="0" parent="02d20bbd7e394">
<name>Object</name> <name>Chapter 2</name>
<type>ROOT</type> <type>FILE</type>
<class>OBJECT</class> <class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="c837649cce43f" order="0" parent="02d20bbd7e394">
<name>Scene 2.1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="6208ef0f7750c" order="0" parent="02d20bbd7e394">
<name>Scene 2.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="3e1e967e9b793" order="0" parent="02d20bbd7e394">
<name>Scene 2.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="39fa9ec190eee" order="0" parent="73475cb40a568">
<name>Chapter 3</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="41cfc0d1f2d12" order="0" parent="None"> <item handle="d029fa3a95e17" order="0" parent="39fa9ec190eee">
<name>Custom1</name> <name>Chapter 3</name>
<type>ROOT</type> <type>FILE</type>
<class>CUSTOM</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item> </item>
<item handle="2858dcd1057d3" order="0" parent="None"> <item handle="81b8a03f97e87" order="0" parent="39fa9ec190eee">
<name>Custom2</name> <name>Scene 3.1</name>
<type>ROOT</type> <type>FILE</type>
<class>CUSTOM</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="da4ea2a5506f2" order="0" parent="39fa9ec190eee">
<name>Scene 3.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="a68b412c42825" order="0" parent="39fa9ec190eee">
<name>Scene 3.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+74 -34
View File
@@ -1,9 +1,11 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43"> <novelWriterXML appVersion="1.0b3" hexVersion="0x010000b3" fileVersion="1.2" timeStamp="2020-09-29 21:12:51">
<project> <project>
<name>New Project</name> <name>Test Custom</name>
<title></title> <title>Test Novel</title>
<saveCount>2</saveCount> <author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount> <autoCount>1</autoCount>
<editTime>0</editTime> <editTime>0</editTime>
</project> </project>
@@ -38,7 +40,7 @@
<entry blue="0" green="200" red="50">Main</entry> <entry blue="0" green="200" red="50">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="10"> <content count="14">
<item handle="73475cb40a568" order="0" parent="None"> <item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name> <name>Novel</name>
<type>ROOT</type> <type>ROOT</type>
@@ -61,13 +63,34 @@
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="811786ad1ae74" order="0" parent="None"> <item handle="811786ad1ae74" order="0" parent="None">
<name>World</name> <name>Locations</name>
<type>ROOT</type> <type>ROOT</type>
<class>WORLD</class> <class>WORLD</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568"> <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="0" parent="None">
<name>Objects</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<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="0" parent="73475cb40a568">
<name>Title Page</name> <name>Title Page</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
@@ -79,27 +102,8 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="31489056e0916" order="0" parent="73475cb40a568"> <item handle="1a6562590ef19" order="0" parent="73475cb40a568">
<name>New Chapter</name> <name>Scene 1</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -110,8 +114,8 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="1a6562590ef19" order="0" parent="31489056e0916"> <item handle="031b4af5197ec" order="0" parent="73475cb40a568">
<name>Hello</name> <name>Scene 2</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -122,13 +126,49 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="031b4af5197ec" order="0" parent="71ee45a3c0db9"> <item handle="41cfc0d1f2d12" order="0" parent="73475cb40a568">
<name>Jane</name> <name>Scene 3</name>
<type>FILE</type> <type>FILE</type>
<class>CHARACTER</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
<exported>True</exported> <exported>True</exported>
<layout>NOTE</layout> <layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2858dcd1057d3" order="0" parent="73475cb40a568">
<name>Scene 4</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2fca346db6561" order="0" parent="73475cb40a568">
<name>Scene 5</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="02d20bbd7e394" order="0" parent="73475cb40a568">
<name>Scene 6</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount> <charCount>0</charCount>
<wordCount>0</wordCount> <wordCount>0</wordCount>
<paraCount>0</paraCount> <paraCount>0</paraCount>
+30 -159
View File
@@ -1,11 +1,9 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43"> <novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43">
<project> <project>
<name>Test Custom</name> <name>New Project</name>
<title>Test Novel</title> <title></title>
<author>Jane Doe</author> <saveCount>2</saveCount>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount> <autoCount>1</autoCount>
<editTime>0</editTime> <editTime>0</editTime>
</project> </project>
@@ -40,7 +38,7 @@
<entry blue="0" green="200" red="50">Main</entry> <entry blue="0" green="200" red="50">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="23"> <content count="12">
<item handle="73475cb40a568" order="0" parent="None"> <item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name> <name>Novel</name>
<type>ROOT</type> <type>ROOT</type>
@@ -63,34 +61,13 @@
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="811786ad1ae74" order="0" parent="None"> <item handle="811786ad1ae74" order="0" parent="None">
<name>Locations</name> <name>World</name>
<type>ROOT</type> <type>ROOT</type>
<class>WORLD</class> <class>WORLD</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="25fc0e7096fc6" order="0" parent="None"> <item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>New</status>
<expanded>False</expanded>
</item>
<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="0" parent="None">
<name>Entity</name>
<type>ROOT</type>
<class>ENTITY</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="0e17daca5f3e1" order="0" parent="73475cb40a568">
<name>Title Page</name> <name>Title Page</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
@@ -102,15 +79,15 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="1a6562590ef19" order="0" parent="73475cb40a568"> <item handle="31489056e0916" order="0" parent="73475cb40a568">
<name>Chapter 1</name> <name>New Chapter</name>
<type>FOLDER</type> <type>FOLDER</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="031b4af5197ec" order="0" parent="1a6562590ef19"> <item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>Chapter 1</name> <name>New Chapter</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -121,8 +98,8 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="41cfc0d1f2d12" order="0" parent="1a6562590ef19"> <item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
<name>Scene 1.1</name> <name>New Scene</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -133,139 +110,33 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="2858dcd1057d3" order="0" parent="1a6562590ef19"> <item handle="1a6562590ef19" order="0" parent="None">
<name>Scene 1.2</name> <name>Timeline</name>
<type>FILE</type> <type>ROOT</type>
<class>NOVEL</class> <class>TIMELINE</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2fca346db6561" order="0" parent="1a6562590ef19">
<name>Scene 1.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="02d20bbd7e394" order="0" parent="73475cb40a568">
<name>Chapter 2</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="7688b6ef52555" order="0" parent="02d20bbd7e394"> <item handle="031b4af5197ec" order="0" parent="None">
<name>Chapter 2</name> <name>Object</name>
<type>FILE</type> <type>ROOT</type>
<class>NOVEL</class> <class>OBJECT</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="c837649cce43f" order="0" parent="02d20bbd7e394">
<name>Scene 2.1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="6208ef0f7750c" order="0" parent="02d20bbd7e394">
<name>Scene 2.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="3e1e967e9b793" order="0" parent="02d20bbd7e394">
<name>Scene 2.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="39fa9ec190eee" order="0" parent="73475cb40a568">
<name>Chapter 3</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="d029fa3a95e17" order="0" parent="39fa9ec190eee"> <item handle="41cfc0d1f2d12" order="0" parent="None">
<name>Chapter 3</name> <name>Custom1</name>
<type>FILE</type> <type>ROOT</type>
<class>NOVEL</class> <class>CUSTOM</class>
<status>New</status> <status>New</status>
<exported>True</exported> <expanded>False</expanded>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item> </item>
<item handle="81b8a03f97e87" order="0" parent="39fa9ec190eee"> <item handle="2858dcd1057d3" order="0" parent="None">
<name>Scene 3.1</name> <name>Custom2</name>
<type>FILE</type> <type>ROOT</type>
<class>NOVEL</class> <class>CUSTOM</class>
<status>New</status> <status>New</status>
<exported>True</exported> <expanded>False</expanded>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="da4ea2a5506f2" order="0" parent="39fa9ec190eee">
<name>Scene 3.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="a68b412c42825" order="0" parent="39fa9ec190eee">
<name>Scene 3.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+34 -74
View File
@@ -1,11 +1,9 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.0b3" hexVersion="0x010000b3" fileVersion="1.2" timeStamp="2020-09-29 21:12:51"> <novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43">
<project> <project>
<name>Test Custom</name> <name>New Project</name>
<title>Test Novel</title> <title></title>
<author>Jane Doe</author> <saveCount>2</saveCount>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount> <autoCount>1</autoCount>
<editTime>0</editTime> <editTime>0</editTime>
</project> </project>
@@ -40,7 +38,7 @@
<entry blue="0" green="200" red="50">Main</entry> <entry blue="0" green="200" red="50">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="14"> <content count="10">
<item handle="73475cb40a568" order="0" parent="None"> <item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name> <name>Novel</name>
<type>ROOT</type> <type>ROOT</type>
@@ -63,34 +61,13 @@
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="811786ad1ae74" order="0" parent="None"> <item handle="811786ad1ae74" order="0" parent="None">
<name>Locations</name> <name>World</name>
<type>ROOT</type> <type>ROOT</type>
<class>WORLD</class> <class>WORLD</class>
<status>New</status> <status>New</status>
<expanded>False</expanded> <expanded>False</expanded>
</item> </item>
<item handle="25fc0e7096fc6" order="0" parent="None"> <item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>New</status>
<expanded>False</expanded>
</item>
<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="0" parent="None">
<name>Entity</name>
<type>ROOT</type>
<class>ENTITY</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="0e17daca5f3e1" order="0" parent="73475cb40a568">
<name>Title Page</name> <name>Title Page</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
@@ -102,8 +79,27 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="1a6562590ef19" order="0" parent="73475cb40a568"> <item handle="31489056e0916" order="0" parent="73475cb40a568">
<name>Scene 1</name> <name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -114,8 +110,8 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="031b4af5197ec" order="0" parent="73475cb40a568"> <item handle="1a6562590ef19" order="0" parent="31489056e0916">
<name>Scene 2</name> <name>Hello</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
<status>New</status> <status>New</status>
@@ -126,49 +122,13 @@
<paraCount>0</paraCount> <paraCount>0</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="41cfc0d1f2d12" order="0" parent="73475cb40a568"> <item handle="031b4af5197ec" order="0" parent="71ee45a3c0db9">
<name>Scene 3</name> <name>Jane</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>CHARACTER</class>
<status>New</status> <status>New</status>
<exported>True</exported> <exported>True</exported>
<layout>SCENE</layout> <layout>NOTE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2858dcd1057d3" order="0" parent="73475cb40a568">
<name>Scene 4</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2fca346db6561" order="0" parent="73475cb40a568">
<name>Scene 5</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="02d20bbd7e394" order="0" parent="73475cb40a568">
<name>Scene 6</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount> <charCount>0</charCount>
<wordCount>0</wordCount> <wordCount>0</wordCount>
<paraCount>0</paraCount> <paraCount>0</paraCount>
+746 -86
View File
@@ -7,15 +7,18 @@ import os
from shutil import copyfile from shutil import copyfile
from zipfile import ZipFile from zipfile import ZipFile
from lxml import etree
from tools import cmpFiles from tools import cmpFiles, writeFile, readFile
from dummy import causeOSError
from nw.core.project import NWProject from nw.core.project import NWProject
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI): def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
"""Test that a basic project can be created, opened and saved. """Create a new project from a project wizard dictionary. With
default setting, creating a Minimal project.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx")
@@ -29,7 +32,6 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
# Try again with a proper path # Try again with a proper path
assert theProject.newProject({"projPath": fncDir}) assert theProject.newProject({"projPath": fncDir})
assert theProject.setProjectPath(fncDir)
assert theProject.saveProject() assert theProject.saveProject()
assert theProject.closeProject() assert theProject.closeProject()
@@ -59,72 +61,7 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8]) assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
# END Test testCoreProject_NewOpenSave # END Test testCoreProject_NewMinimal
@pytest.mark.core
def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
"""Check that new root folders can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": fncDir})
assert theProject.setProjectPath(fncDir)
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, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
# END Test testCoreProject_NewRoot
@pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
"""Check that new files can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": fncDir})
assert theProject.setProjectPath(fncDir)
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, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
# END Test testCoreProject_NewFile
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
@@ -132,8 +69,8 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
Custom type with chapters and scenes. Custom type with chapters and scenes.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
projData = { projData = {
"projName": "Test Custom", "projName": "Test Custom",
@@ -173,8 +110,8 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
Custom type without chapters, but with scenes. Custom type without chapters, but with scenes.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
projData = { projData = {
"projName": "Test Custom", "projName": "Test Custom",
@@ -296,7 +233,418 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
# END Test testCoreProject_NewSampleB # END Test testCoreProject_NewSampleB
@pytest.mark.core @pytest.mark.core
def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI): def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
"""Check that new root folders can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": fncDir})
assert theProject.setProjectPath(fncDir)
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, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
# END Test testCoreProject_NewRoot
@pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
"""Check that new files can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": fncDir})
assert theProject.setProjectPath(fncDir)
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, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
# END Test testCoreProject_NewFile
@pytest.mark.core
def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
"""Test opening a project.
"""
theProject = NWProject(dummyGUI)
# Rename the project file to check handling
rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE)
wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj")
os.rename(rName, wName)
assert theProject.openProject(nwMinimal) is False
os.rename(wName, rName)
# Fail on folder structure check
monkeypatch.setattr("os.mkdir", causeOSError)
assert theProject.openProject(nwMinimal) is False
monkeypatch.undo()
# Fail on lock file
theProject.setProjectPath(nwMinimal)
assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal) is False
# Fail to read lockfile (which still opens the project)
monkeypatch.setattr("builtins.open", causeOSError)
assert theProject.openProject(nwMinimal) is True
monkeypatch.undo()
assert theProject.closeProject()
# Force open with lockfile
theProject.setProjectPath(nwMinimal)
assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal, overrideLock=True) is True
assert theProject.closeProject()
# Make a junk XML file
oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"orig")
bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"bak")
os.rename(rName, oName)
writeFile(rName, "dummy")
assert theProject.openProject(nwMinimal) is False
# Also write a jun XML backup file
writeFile(bName, "dummy")
assert theProject.openProject(nwMinimal) is False
# Wrong root item
writeFile(rName, "<not_novelWriterXML></not_novelWriterXML>\n")
assert theProject.openProject(nwMinimal) is False
# Wrong file version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"1.0\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
dummyGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
dummyGUI.undo()
# Future file version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"99.99\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is False
# Larger hex version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0xffffffff\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
dummyGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
dummyGUI.undo()
# Test skipping XML entries
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"<project><stuff/></project>\n"
"<settings><stuff/></settings>\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is True
assert theProject.closeProject()
# Test deprecated XML entries
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"<settings>\n"
"<autoReplace>\n"
"<A>B</A>\n"
"</autoReplace>\n"
"</settings>\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is True
assert theProject.autoReplace == {"A": "B"}
assert theProject.closeProject()
# Clean up XML files
os.unlink(rName)
os.unlink(bName)
os.rename(oName, rName)
# Add some legacy stuff that cannot be removed
writeFile(os.path.join(nwMinimal, "junk"), "dummy")
os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "junk"), "dummy")
dummyGUI.clear()
assert theProject.openProject(nwMinimal) is True
assert "data_0" in dummyGUI.lastAlert
assert theProject.closeProject()
# END Test testCoreProject_Open
@pytest.mark.core
def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir):
"""Test saving a project.
"""
theProject = NWProject(dummyGUI)
testFile = os.path.join(nwMinimal, "nwProject.nwx")
compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx")
# Nothing to save
assert theProject.saveProject() is False
# Open test project
assert theProject.openProject(nwMinimal)
# Fail on folder structure check
monkeypatch.setattr("os.path.isdir", lambda *args: False)
assert theProject.saveProject() is False
monkeypatch.undo()
# Fail on open file
monkeypatch.setattr("builtins.open", causeOSError)
assert theProject.saveProject() is False
monkeypatch.undo()
# Successful save
saveCount = theProject.saveCount
autoCount = theProject.autoCount
assert theProject.saveProject() is True
assert theProject.saveCount == saveCount + 1
assert theProject.autoCount == autoCount
assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9])
# Successful autosave
saveCount = theProject.saveCount
autoCount = theProject.autoCount
assert theProject.saveProject(autoSave=True) is True
assert theProject.saveCount == saveCount
assert theProject.autoCount == autoCount + 1
assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9])
# Close test project
assert theProject.closeProject()
# END Test testCoreProject_Save
@pytest.mark.core
def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
"""Test lock file functions for the project folder.
"""
theProject = NWProject(dummyGUI)
lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK)
# No project
assert theProject._writeLockFile() is False
assert theProject._readLockFile() == ["ERROR"]
assert theProject._clearLockFile() is False
theProject.projPath = fncDir
theProject.mainConf.hostName = "TestHost"
theProject.mainConf.osType = "TestOS"
theProject.mainConf.kernelVer = "1.0"
# Block open
monkeypatch.setattr("builtins.open", causeOSError)
assert theProject._writeLockFile() is False
monkeypatch.undo()
# Write lock file
monkeypatch.setattr("nw.core.project.time", lambda: 123.4)
assert theProject._writeLockFile() is True
monkeypatch.undo()
assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n"
# Block open
monkeypatch.setattr("builtins.open", causeOSError)
assert theProject._readLockFile() == ["ERROR"]
monkeypatch.undo()
# Read lock file
assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"]
# Block unlink
monkeypatch.setattr("os.unlink", causeOSError)
assert os.path.isfile(lockFile)
assert theProject._clearLockFile() is False
assert os.path.isfile(lockFile)
monkeypatch.undo()
# Clear file
assert os.path.isfile(lockFile)
assert theProject._clearLockFile() is True
assert not os.path.isfile(lockFile)
# Read again, no file
assert theProject._readLockFile() == []
# Read an invalid lock file
writeFile(lockFile, "A\nB")
assert theProject._readLockFile() == ["ERROR"]
assert theProject._clearLockFile() is True
# END Test testCoreProject_LockFile
@pytest.mark.core
def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
"""Test helper functions for the project folder.
"""
theProject = NWProject(dummyGUI)
# No path
assert theProject.ensureFolderStructure() is False
# Set the correct dir
theProject.projPath = fncDir
# Block user's home folder
monkeypatch.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir)
assert theProject.ensureFolderStructure() is False
monkeypatch.undo()
# Create a file to block meta folder
metaDir = os.path.join(fncDir, "meta")
writeFile(metaDir, "dummy")
assert theProject.ensureFolderStructure() is False
os.unlink(metaDir)
# Create a file to block cache folder
cacheDir = os.path.join(fncDir, "cache")
writeFile(cacheDir, "dummy")
assert theProject.ensureFolderStructure() is False
os.unlink(cacheDir)
# Create a file to block content folder
contentDir = os.path.join(fncDir, "content")
writeFile(contentDir, "dummy")
assert theProject.ensureFolderStructure() is False
os.unlink(contentDir)
# Now, do it right
assert theProject.ensureFolderStructure() is True
assert os.path.isdir(metaDir)
assert os.path.isdir(cacheDir)
assert os.path.isdir(contentDir)
# END Test testCoreProject_Helpers
@pytest.mark.core
def testCoreProject_AccessItems(nwMinimal, dummyGUI):
"""Test helper functions for the project folder.
"""
theProject = NWProject(dummyGUI)
theProject.openProject(nwMinimal)
# Move Novel ROOT to after its files
oldOrder = [
"a508bb932959c", # ROOT: Novel
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
]
newOrder = [
"a35baf2e93843", # FILE: Title Page
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"a6d311a93600a", # FOLDER: New Chapter
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
]
assert theProject.projTree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
assert theProject.projTree.handles() == newOrder
# Add a non-existing item
theProject.projTree._treeOrder.append("01234567789abc")
# Add an item with a non-existent parent
nHandle = theProject.newFile("Test File", nwItemClass.NOVEL, "a6d311a93600a")
theProject.projTree[nHandle].setParent("cba9876543210")
assert theProject.projTree[nHandle].itemParent == "cba9876543210"
retOrder = []
for tItem in theProject.getProjectItems():
retOrder.append(tItem.itemHandle)
assert retOrder == [
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
nHandle, # FILE: Test File
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
]
assert theProject.projTree[nHandle].itemParent is None
# END Test testCoreProject_AccessItems
@pytest.mark.core
def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
"""Test other project class methods and functions. """Test other project class methods and functions.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
@@ -317,13 +665,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
assert theProject.setProjectPath(projPath, newProject=True) assert theProject.setProjectPath(projPath, newProject=True)
# Make os.mkdir fail # Make os.mkdir fail
def altMkdir(*args): monkeypatch.setattr("os.mkdir", causeOSError)
raise Exception("Oops!")
monkeypatch.setattr("os.mkdir", altMkdir)
projPath = os.path.join(nwMinimal, "dummy2") projPath = os.path.join(nwMinimal, "dummy2")
assert not theProject.setProjectPath(projPath, newProject=True) assert not theProject.setProjectPath(projPath, newProject=True)
# Set back
assert theProject.setProjectPath(nwMinimal)
# Project Name # Project Name
assert theProject.setProjectName(" A Name ") assert theProject.setProjectName(" A Name ")
assert theProject.projName == "A Name" assert theProject.projName == "A Name"
@@ -337,6 +685,191 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
assert theProject.bookAuthors == ["Jane Doe", "John Doh"] assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
# Trash folder
# Should create on first call, and just returned on later calls
assert theProject.projTree["73475cb40a568"] is None
assert theProject.trashFolder() == "73475cb40a568"
assert theProject.trashFolder() == "73475cb40a568"
# Project backup
assert theProject.doBackup is True
assert theProject.setProjBackup(False)
assert theProject.doBackup is False
assert not theProject.setProjBackup(True)
theProject.mainConf.backupPath = tmpDir
assert theProject.setProjBackup(True)
assert theProject.setProjectName("")
assert not theProject.setProjBackup(True)
assert theProject.setProjectName("A Name")
assert theProject.setProjBackup(True)
# Spell check
theProject.projChanged = False
assert theProject.setSpellCheck(True)
assert not theProject.setSpellCheck(False)
assert theProject.projChanged
# Spell language
theProject.projChanged = False
assert theProject.setSpellLang(None)
assert theProject.projLang is None
assert theProject.setSpellLang("None")
assert theProject.projLang is None
assert theProject.setSpellLang("en_GB")
assert theProject.projLang == "en_GB"
assert theProject.projChanged
# Automatic outline update
theProject.projChanged = False
assert theProject.setAutoOutline(True)
assert not theProject.setAutoOutline(False)
assert theProject.projChanged
# Last edited
theProject.projChanged = False
assert theProject.setLastEdited("0123456789abc")
assert theProject.lastEdited == "0123456789abc"
assert theProject.projChanged
# Last viewed
theProject.projChanged = False
assert theProject.setLastViewed("0123456789abc")
assert theProject.lastViewed == "0123456789abc"
assert theProject.projChanged
# Autoreplace
theProject.projChanged = False
assert theProject.setAutoReplace({"A": "B", "C": "D"})
assert theProject.autoReplace == {"A": "B", "C": "D"}
assert theProject.projChanged
# Change project tree order
oldOrder = [
"a508bb932959c", "a35baf2e93843", "a6d311a93600a",
"f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
"afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568",
]
newOrder = [
"f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
"a508bb932959c", "a35baf2e93843", "a6d311a93600a",
"afb3043c7b2b3", "9d5247ab588e0",
]
assert theProject.projTree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
assert theProject.projTree.handles() == newOrder
assert theProject.setTreeOrder(oldOrder)
assert theProject.projTree.handles() == oldOrder
# Change status
theProject.projTree["a35baf2e93843"].setStatus("Finished")
theProject.projTree["a6d311a93600a"].setStatus("Draft")
theProject.projTree["f5ab3e30151e1"].setStatus("Note")
theProject.projTree["8c659a11cd429"].setStatus("Finished")
newList = [
("New", 1, 1, 1, "New"),
("Draft", 2, 2, 2, "Note"), # These are swapped
("Note", 3, 3, 3, "Draft"), # These are swapped
("Edited", 4, 4, 4, "Finished"), # Renamed
("Finished", 5, 5, 5, None), # New, with reused name
]
assert theProject.setStatusColours(newList)
assert theProject.statusItems._theLabels == [
"New", "Draft", "Note", "Edited", "Finished"
]
assert theProject.statusItems._theColours == [
(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
]
assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
# Change importance
fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
theProject.projTree[fHandle].setStatus("Main")
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 theProject.setImportColours(newList)
assert theProject.importItems._theLabels == [
"New", "Minor", "Major", "Min", "Max"
]
assert theProject.importItems._theColours == [
(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
]
assert theProject.projTree[fHandle].itemStatus == "Min"
# Check status counts
assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0]
assert theProject.importItems._theCounts == [0, 0, 0, 0, 0]
theProject.countStatus()
assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]
# Check word counts
theProject.currWCount = 200
theProject.lastWCount = 100
assert theProject.getSessionWordCount() == 100
# Session stats
monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False)
assert not theProject._appendSessionStats()
monkeypatch.undo()
# Block open
monkeypatch.setattr("builtins.open", causeOSError)
assert not theProject._appendSessionStats()
monkeypatch.undo()
# Write entry
assert theProject.projMeta == os.path.join(nwMinimal, "meta")
statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000
theProject.novelWCount = 200
theProject.notesWCount = 100
monkeypatch.setattr("nw.core.project.time", lambda: 1600005600)
assert theProject._appendSessionStats()
monkeypatch.undo()
assert readFile(statsFile) == (
"# Offset 100\n"
"# Start Time End Time Novel Notes\n"
"2020-09-13 13:00:00 2020-09-13 14:00:00 200 100\n"
)
# Pack XML Value
xElem = etree.Element("element")
theProject._packProjectValue(xElem, "A", "B", allowNone=False)
assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
b"<element><A>B</A></element>"
)
xElem = etree.Element("element")
theProject._packProjectValue(xElem, "A", "", allowNone=False)
assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
b"<element/>"
)
# Pack XML Key/Value
xElem = etree.Element("element")
theProject._packProjectKeyValue(xElem, "item", {"A": "B", "C": "D"})
assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
b"<element>"
b"<item>"
b"<entry key=\"A\">B</entry>"
b"<entry key=\"C\">D</entry>"
b"</item>"
b"</element>"
)
# END Test testCoreProject_Methods # END Test testCoreProject_Methods
@pytest.mark.core @pytest.mark.core
@@ -347,6 +880,7 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
the meta line at the top of the document file. the meta line at the top of the document file.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
assert theProject.projTree["636b6aa9b697b"] is None assert theProject.projTree["636b6aa9b697b"] is None
assert theProject.closeProject() assert theProject.closeProject()
@@ -408,6 +942,10 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
assert theProject.saveProject(nwLipsum) assert theProject.saveProject(nwLipsum)
assert theProject.closeProject() assert theProject.closeProject()
# Finally, check that the orphaned files function returns
# if no project is open and no path is set
assert not theProject._scanProjectFolder()
# END Test testCoreProject_OrphanedFiles # END Test testCoreProject_OrphanedFiles
@pytest.mark.core @pytest.mark.core
@@ -418,7 +956,6 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
contained in a single 'content' folder. contained in a single 'content' folder.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
theProject.mainConf.showGUI = False
# Create dummy files for known legacy files # Create dummy files for known legacy files
deleteFiles = [ deleteFiles = [
@@ -451,8 +988,7 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
# Create dummy files # Create dummy files
os.mkdir(os.path.join(nwOldProj, "cache")) os.mkdir(os.path.join(nwOldProj, "cache"))
for aFile in deleteFiles: for aFile in deleteFiles:
with open(aFile, mode="w+", encoding="utf8") as outFile: writeFile(aFile, "Hi")
outFile.write("Hi")
for aFile in deleteFiles: for aFile in deleteFiles:
assert os.path.isfile(aFile) assert os.path.isfile(aFile)
@@ -503,7 +1039,112 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
# END Test testCoreProject_OldFormat # END Test testCoreProject_OldFormat
@pytest.mark.core @pytest.mark.core
def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir): def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
"""Test the functins that handle legacy data folders and structure
with additional tests of failure handling.
"""
theProject = NWProject(dummyGUI)
theProject.setProjectPath(fncDir)
# assert theProject.newProject({"projPath": fncDir})
# assert theProject.saveProject()
# assert theProject.closeProject()
# Check behaviour of deprecated files function on OSError
tstFile = os.path.join(fncDir, "ToC.json")
writeFile(tstFile, "dummy")
assert os.path.isfile(tstFile)
monkeypatch.setattr("os.unlink", causeOSError)
assert not theProject._deprecatedFiles()
monkeypatch.undo()
assert theProject._deprecatedFiles()
assert not os.path.isfile(tstFile)
# Check processing non-folders
tstFile = os.path.join(fncDir, "data_0")
writeFile(tstFile, "dummy")
assert os.path.isfile(tstFile)
errList = []
errList = theProject._legacyDataFolder(tstFile, errList)
assert len(errList) > 0
# Move folder in data folder, shouldn't be there
tstData = os.path.join(fncDir, "data_1")
errItem = os.path.join(fncDir, "data_1", "stuff")
os.mkdir(tstData)
os.mkdir(errItem)
assert os.path.isdir(tstData)
assert os.path.isdir(errItem)
# This causes a failure to create the 'junk' folder
monkeypatch.setattr("os.mkdir", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
monkeypatch.undo()
# This causes a failure to move 'stuff' to 'junk'
monkeypatch.setattr("os.rename", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
monkeypatch.undo()
# This should be successful
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) == 0
assert os.path.isdir(os.path.join(fncDir, "junk", "stuff"))
# Check renaming/deleting of old document files
tstData = os.path.join(fncDir, "data_2")
tstDoc1m = os.path.join(tstData, "000000000001_main.nwd")
tstDoc1b = os.path.join(tstData, "000000000001_main.bak")
tstDoc2m = os.path.join(tstData, "000000000002_main.nwd")
tstDoc2b = os.path.join(tstData, "000000000002_main.bak")
tstDoc3m = os.path.join(tstData, "tooshort003_main.nwd")
tstDoc3b = os.path.join(tstData, "tooshort003_main.bak")
os.mkdir(tstData)
writeFile(tstDoc1m, "dummy")
writeFile(tstDoc1b, "dummy")
writeFile(tstDoc2m, "dummy")
writeFile(tstDoc2b, "dummy")
writeFile(tstDoc3m, "dummy")
writeFile(tstDoc3b, "dummy")
# Make the above fail
monkeypatch.setattr("os.rename", causeOSError)
monkeypatch.setattr("os.unlink", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
assert os.path.isfile(tstDoc1m)
assert os.path.isfile(tstDoc1b)
assert os.path.isfile(tstDoc2m)
assert os.path.isfile(tstDoc2b)
assert os.path.isfile(tstDoc3m)
assert os.path.isfile(tstDoc3b)
monkeypatch.undo()
# And succeed ...
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) == 0
assert not os.path.isdir(tstData)
assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd"))
assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd"))
assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.nwd"))
assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.bak"))
# END Test testCoreProject_LegacyData
@pytest.mark.core
def testCoreProject_Backup(monkeypatch, dummyGUI, nwMinimal, tmpDir):
"""Test the automated backup feature of the project class. The test """Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to backupd file and checks that the project XML file is identical to
@@ -513,6 +1154,12 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
# Test faulty settings # Test faulty settings
# No project
dummyGUI.hasProject = False
assert not theProject.zipIt(doNotify=False)
dummyGUI.hasProject = True
# Invalid path # Invalid path
theProject.mainConf.backupPath = None theProject.mainConf.backupPath = None
assert not theProject.zipIt(doNotify=False) assert not theProject.zipIt(doNotify=False)
@@ -531,9 +1178,21 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
theProject.mainConf.backupPath = nwMinimal theProject.mainConf.backupPath = nwMinimal
assert not theProject.zipIt(doNotify=False) assert not theProject.zipIt(doNotify=False)
# Test correct settings # Set a valid folder
theProject.mainConf.backupPath = tmpDir theProject.mainConf.backupPath = tmpDir
assert theProject.zipIt(doNotify=False)
# Can't make folder
monkeypatch.setattr("os.mkdir", causeOSError)
assert not theProject.zipIt(doNotify=False)
monkeypatch.undo()
# Can't write archive
monkeypatch.setattr("shutil.make_archive", causeOSError)
assert not theProject.zipIt(doNotify=False)
monkeypatch.undo()
# Test correct settings
assert theProject.zipIt(doNotify=True)
theFiles = os.listdir(os.path.join(tmpDir, "Test Minimal")) theFiles = os.listdir(os.path.join(tmpDir, "Test Minimal"))
assert len(theFiles) == 1 assert len(theFiles) == 1
@@ -548,7 +1207,8 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
# Check that the main project file was restored # Check that the main project file was restored
assert cmpFiles( assert cmpFiles(
os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx") os.path.join(nwMinimal, "nwProject.nwx"),
os.path.join(tmpDir, "extract", "nwProject.nwx")
) )
# END Test testCoreProject_Backup # END Test testCoreProject_Backup
+3 -3
View File
@@ -10,7 +10,7 @@ from lxml import etree
from nw.core.project import NWProject, NWItem, NWTree from nw.core.project import NWProject, NWItem, NWTree
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
@pytest.fixture(scope="session") @pytest.fixture(scope="function")
def dummyItems(dummyGUI): def dummyItems(dummyGUI):
"""Create a list of dummy items. """Create a list of dummy items.
""" """
@@ -353,7 +353,7 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
b"<expanded>True</expanded></item>" b"<expanded>True</expanded></item>"
b"<item handle=\"c000000000001\" order=\"0\" parent=\"b000000000001\">" b"<item handle=\"c000000000001\" order=\"0\" parent=\"b000000000001\">"
b"<name>Chapter One</name><type>FILE</type><class>NOVEL</class><status>None</status>" b"<name>Chapter One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
b"<exported>True</exported><layout>UNNUMBERED</layout><charCount>300</charCount>" b"<exported>True</exported><layout>CHAPTER</layout><charCount>300</charCount>"
b"<wordCount>50</wordCount><paraCount>2</paraCount><cursorPos>0</cursorPos></item>" b"<wordCount>50</wordCount><paraCount>2</paraCount><cursorPos>0</cursorPos></item>"
b"<item handle=\"c000000000002\" order=\"0\" parent=\"b000000000001\">" b"<item handle=\"c000000000002\" order=\"0\" parent=\"b000000000001\">"
b"<name>Scene One</name><type>FILE</type><class>NOVEL</class><status>None</status>" b"<name>Scene One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
@@ -425,7 +425,7 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir):
"\n" "\n"
"File Name Class Layout Document Label\n" "File Name Class Layout Document Label\n"
"-------------------------------------------------------------\n" "-------------------------------------------------------------\n"
f"{pathA} NOVEL UNNUMBERED Chapter One\n" f"{pathA} NOVEL CHAPTER Chapter One\n"
f"{pathB} NOVEL SCENE Scene One\n" f"{pathB} NOVEL SCENE Scene One\n"
f"{pathC} CHARACTER NOTE Jane Doe\n" f"{pathC} CHARACTER NOTE Jane Doe\n"
) )