Updated and new tests for NWProject class
This commit is contained in:
+27
-24
@@ -1125,13 +1125,11 @@ class NWProject():
|
||||
sentItems = []
|
||||
iterItems = self.projTree.handles()
|
||||
n = 0
|
||||
nMax = len(iterItems)
|
||||
nMax = min(len(iterItems), 10000)
|
||||
while n < nMax:
|
||||
tHandle = iterItems[n]
|
||||
tItem = self.projTree[tHandle]
|
||||
n += 1
|
||||
if n > 10000:
|
||||
return # Just in case
|
||||
if tItem is None:
|
||||
# Technically a bug since treeOrder is built from the
|
||||
# same data as projTree
|
||||
@@ -1147,10 +1145,11 @@ class NWProject():
|
||||
yield tItem
|
||||
elif tItem.itemParent in iterItems:
|
||||
# Item's parent exists, but hasn't been sent yet, so add
|
||||
# it again to the end
|
||||
# it again to the end, but make sure this doesn't get
|
||||
# out hand, so we cap at 10000 items
|
||||
logger.warning("Item %s found before its parent" % tHandle)
|
||||
iterItems.append(tHandle)
|
||||
nMax = len(iterItems)
|
||||
nMax = min(len(iterItems), 10000)
|
||||
else:
|
||||
# Item is orphaned
|
||||
logger.error("Item %s has no parent in current tree" % tHandle)
|
||||
@@ -1189,13 +1188,12 @@ class NWProject():
|
||||
if not os.path.isfile(lockFile):
|
||||
return []
|
||||
|
||||
theLines = []
|
||||
try:
|
||||
with open(lockFile, mode="r", encoding="utf8") as inFile:
|
||||
theData = inFile.read()
|
||||
theLines = theData.splitlines()
|
||||
if len(theLines) == 4:
|
||||
return theLines
|
||||
else:
|
||||
if len(theLines) != 4:
|
||||
return ["ERROR"]
|
||||
|
||||
except Exception as e:
|
||||
@@ -1203,7 +1201,7 @@ class NWProject():
|
||||
logger.error(str(e))
|
||||
return ["ERROR"]
|
||||
|
||||
return ["ERROR"]
|
||||
return theLines
|
||||
|
||||
def _writeLockFile(self):
|
||||
"""Writes a lock file to the project folder.
|
||||
@@ -1236,13 +1234,12 @@ class NWProject():
|
||||
if os.path.isfile(lockFile):
|
||||
try:
|
||||
os.unlink(lockFile)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Failed to remove project lockfile")
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return None
|
||||
return True
|
||||
|
||||
def _checkFolder(self, thePath):
|
||||
"""Check if a folder exists, and if it doesn't, create it.
|
||||
@@ -1356,21 +1353,27 @@ class NWProject():
|
||||
sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS)
|
||||
isFile = os.path.isfile(sessionFile)
|
||||
|
||||
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
|
||||
if not isFile:
|
||||
# It's a new file, so add a header
|
||||
if self.lastWCount > 0:
|
||||
outFile.write("# Offset %d\n" % self.lastWCount)
|
||||
outFile.write("# %-17s %-19s %8s %8s\n" % (
|
||||
"Start Time", "End Time", "Novel", "Notes"
|
||||
try:
|
||||
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
|
||||
if not isFile:
|
||||
# It's a new file, so add a header
|
||||
if self.lastWCount > 0:
|
||||
outFile.write("# Offset %d\n" % self.lastWCount)
|
||||
outFile.write("# %-17s %-19s %8s %8s\n" % (
|
||||
"Start Time", "End Time", "Novel", "Notes"
|
||||
))
|
||||
|
||||
outFile.write("%-19s %-19s %8d %8d\n" % (
|
||||
formatTimeStamp(self.projOpened),
|
||||
formatTimeStamp(time()),
|
||||
self.novelWCount,
|
||||
self.notesWCount,
|
||||
))
|
||||
|
||||
outFile.write("%-19s %-19s %8d %8d\n" % (
|
||||
formatTimeStamp(self.projOpened),
|
||||
formatTimeStamp(time()),
|
||||
self.novelWCount,
|
||||
self.notesWCount,
|
||||
))
|
||||
except Exception as e:
|
||||
logger.error("Failed to write session stats file")
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -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 | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` |
|
||||
| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` |
|
||||
| Unit | NWProject class | nw/core/project.py | `-m core` | `-k testCoreProject` |
|
||||
| Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
|
||||
| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
|
||||
| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
|
||||
|
||||
+7
-2
@@ -6,12 +6,15 @@ import sys
|
||||
import pytest
|
||||
import shutil
|
||||
import os
|
||||
import time
|
||||
|
||||
from dummy import DummyMain
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
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
|
||||
|
||||
@@ -69,7 +72,7 @@ def fncDir(tmpDir):
|
||||
# novelWriter Objects
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture(scope="function")
|
||||
def tmpConf(tmpDir):
|
||||
"""Create a temporary novelWriter configuration object.
|
||||
"""
|
||||
@@ -78,7 +81,7 @@ def tmpConf(tmpDir):
|
||||
theConf.setLastPath("")
|
||||
return theConf
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture(scope="function")
|
||||
def dummyGUI(tmpConf):
|
||||
"""Create a dummy instance of novelWriter's main GUI class.
|
||||
"""
|
||||
@@ -168,6 +171,8 @@ def yesToAll(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes
|
||||
)
|
||||
yield
|
||||
monkeypatch.undo()
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
|
||||
@@ -14,12 +14,22 @@ class DummyMain():
|
||||
self.theIndex = None
|
||||
self.theProject = None
|
||||
self.statusBar = StatusBar()
|
||||
|
||||
# Test Variables
|
||||
self.askResponse = True
|
||||
self.lastAlert = ""
|
||||
|
||||
return
|
||||
|
||||
def makeAlert(self, theMessage, theLevel):
|
||||
print("%s: %s" % (str(theLevel), theMessage))
|
||||
self.lastAlert = str(theMessage)
|
||||
return
|
||||
|
||||
def askQuestion(self, theTitle, theQustion):
|
||||
print("Question: %s" % theQustion)
|
||||
return self.askResponse
|
||||
|
||||
def setStatus(self, theMessage):
|
||||
return
|
||||
|
||||
@@ -32,6 +42,16 @@ class DummyMain():
|
||||
def rebuildIndex(self):
|
||||
return
|
||||
|
||||
# Test Functions
|
||||
|
||||
def undo(self):
|
||||
self.askResponse = True
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
self.lastAlert = ""
|
||||
return
|
||||
|
||||
# END Class GuiMain
|
||||
|
||||
class StatusBar():
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<saveCount>2</saveCount>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<saveCount>1</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
@@ -38,7 +40,7 @@
|
||||
<entry blue="0" green="200" red="50">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="12">
|
||||
<content count="23">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
@@ -61,13 +63,34 @@
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>World</name>
|
||||
<name>Locations</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</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>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -79,15 +102,15 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<item handle="1a6562590ef19" order="0" parent="73475cb40a568">
|
||||
<name>Chapter 1</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
||||
<name>New Chapter</name>
|
||||
<item handle="031b4af5197ec" order="0" parent="1a6562590ef19">
|
||||
<name>Chapter 1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -98,8 +121,8 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
|
||||
<name>New Scene</name>
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -110,33 +133,139 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="0" parent="None">
|
||||
<name>Timeline</name>
|
||||
<type>ROOT</type>
|
||||
<class>TIMELINE</class>
|
||||
<item handle="2858dcd1057d3" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.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="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>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="0" parent="None">
|
||||
<name>Object</name>
|
||||
<type>ROOT</type>
|
||||
<class>OBJECT</class>
|
||||
<item handle="7688b6ef52555" order="0" parent="02d20bbd7e394">
|
||||
<name>Chapter 2</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="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>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="None">
|
||||
<name>Custom1</name>
|
||||
<type>ROOT</type>
|
||||
<class>CUSTOM</class>
|
||||
<item handle="d029fa3a95e17" order="0" parent="39fa9ec190eee">
|
||||
<name>Chapter 3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<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 handle="2858dcd1057d3" order="0" parent="None">
|
||||
<name>Custom2</name>
|
||||
<type>ROOT</type>
|
||||
<class>CUSTOM</class>
|
||||
<item handle="81b8a03f97e87" order="0" parent="39fa9ec190eee">
|
||||
<name>Scene 3.1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<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>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<saveCount>2</saveCount>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<saveCount>1</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
@@ -38,7 +40,7 @@
|
||||
<entry blue="0" green="200" red="50">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="10">
|
||||
<content count="14">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
@@ -61,13 +63,34 @@
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>World</name>
|
||||
<name>Locations</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</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>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -79,27 +102,8 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="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>
|
||||
<item handle="1a6562590ef19" order="0" parent="73475cb40a568">
|
||||
<name>Scene 1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -110,8 +114,8 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="0" parent="31489056e0916">
|
||||
<name>Hello</name>
|
||||
<item handle="031b4af5197ec" order="0" parent="73475cb40a568">
|
||||
<name>Scene 2</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -122,13 +126,49 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="0" parent="71ee45a3c0db9">
|
||||
<name>Jane</name>
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="73475cb40a568">
|
||||
<name>Scene 3</name>
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<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>
|
||||
<wordCount>0</wordCount>
|
||||
<paraCount>0</paraCount>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:31:43">
|
||||
<project>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<saveCount>1</saveCount>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
@@ -40,7 +38,7 @@
|
||||
<entry blue="0" green="200" red="50">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="23">
|
||||
<content count="12">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
@@ -63,34 +61,13 @@
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>Locations</name>
|
||||
<name>World</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<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">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -102,15 +79,15 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="0" parent="73475cb40a568">
|
||||
<name>Chapter 1</name>
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="0" parent="1a6562590ef19">
|
||||
<name>Chapter 1</name>
|
||||
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
||||
<name>New Chapter</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -121,8 +98,8 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.1</name>
|
||||
<item handle="0e17daca5f3e1" order="0" parent="31489056e0916">
|
||||
<name>New Scene</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -133,139 +110,33 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" order="0" parent="1a6562590ef19">
|
||||
<name>Scene 1.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="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>
|
||||
<item handle="1a6562590ef19" order="0" parent="None">
|
||||
<name>Timeline</name>
|
||||
<type>ROOT</type>
|
||||
<class>TIMELINE</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="7688b6ef52555" order="0" parent="02d20bbd7e394">
|
||||
<name>Chapter 2</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="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>
|
||||
<item handle="031b4af5197ec" order="0" parent="None">
|
||||
<name>Object</name>
|
||||
<type>ROOT</type>
|
||||
<class>OBJECT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="d029fa3a95e17" order="0" parent="39fa9ec190eee">
|
||||
<name>Chapter 3</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="None">
|
||||
<name>Custom1</name>
|
||||
<type>ROOT</type>
|
||||
<class>CUSTOM</class>
|
||||
<status>New</status>
|
||||
<exported>True</exported>
|
||||
<layout>CHAPTER</layout>
|
||||
<charCount>0</charCount>
|
||||
<wordCount>0</wordCount>
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="81b8a03f97e87" order="0" parent="39fa9ec190eee">
|
||||
<name>Scene 3.1</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<item handle="2858dcd1057d3" order="0" parent="None">
|
||||
<name>Custom2</name>
|
||||
<type>ROOT</type>
|
||||
<class>CUSTOM</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="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>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?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>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<saveCount>1</saveCount>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
@@ -40,7 +38,7 @@
|
||||
<entry blue="0" green="200" red="50">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="14">
|
||||
<content count="10">
|
||||
<item handle="73475cb40a568" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
@@ -63,34 +61,13 @@
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" order="0" parent="None">
|
||||
<name>Locations</name>
|
||||
<name>World</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<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">
|
||||
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
||||
<name>Title Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -102,8 +79,27 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" order="0" parent="73475cb40a568">
|
||||
<name>Scene 1</name>
|
||||
<item handle="31489056e0916" order="0" parent="73475cb40a568">
|
||||
<name>New Chapter</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" order="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>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -114,8 +110,8 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" order="0" parent="73475cb40a568">
|
||||
<name>Scene 2</name>
|
||||
<item handle="1a6562590ef19" order="0" parent="31489056e0916">
|
||||
<name>Hello</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
@@ -126,49 +122,13 @@
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" order="0" parent="73475cb40a568">
|
||||
<name>Scene 3</name>
|
||||
<item handle="031b4af5197ec" order="0" parent="71ee45a3c0db9">
|
||||
<name>Jane</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<class>CHARACTER</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="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>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>0</charCount>
|
||||
<wordCount>0</wordCount>
|
||||
<paraCount>0</paraCount>
|
||||
|
||||
+746
-86
@@ -7,15 +7,18 @@ import os
|
||||
|
||||
from shutil import copyfile
|
||||
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.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
|
||||
"""Test that a basic project can be created, opened and saved.
|
||||
def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
|
||||
"""Create a new project from a project wizard dictionary. With
|
||||
default setting, creating a Minimal project.
|
||||
"""
|
||||
projFile = os.path.join(fncDir, "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
|
||||
assert theProject.newProject({"projPath": fncDir})
|
||||
assert theProject.setProjectPath(fncDir)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
|
||||
@@ -59,72 +61,7 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
|
||||
|
||||
# END Test testCoreProject_NewOpenSave
|
||||
|
||||
@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
|
||||
# END Test testCoreProject_NewMinimal
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
|
||||
@@ -132,8 +69,8 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
|
||||
Custom type with chapters and scenes.
|
||||
"""
|
||||
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")
|
||||
testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
|
||||
|
||||
projData = {
|
||||
"projName": "Test Custom",
|
||||
@@ -173,8 +110,8 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
|
||||
Custom type without chapters, but with scenes.
|
||||
"""
|
||||
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")
|
||||
testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
|
||||
|
||||
projData = {
|
||||
"projName": "Test Custom",
|
||||
@@ -296,7 +233,418 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
|
||||
# END Test testCoreProject_NewSampleB
|
||||
|
||||
@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.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
@@ -317,13 +665,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
|
||||
assert theProject.setProjectPath(projPath, newProject=True)
|
||||
|
||||
# Make os.mkdir fail
|
||||
def altMkdir(*args):
|
||||
raise Exception("Oops!")
|
||||
|
||||
monkeypatch.setattr("os.mkdir", altMkdir)
|
||||
monkeypatch.setattr("os.mkdir", causeOSError)
|
||||
projPath = os.path.join(nwMinimal, "dummy2")
|
||||
assert not theProject.setProjectPath(projPath, newProject=True)
|
||||
|
||||
# Set back
|
||||
assert theProject.setProjectPath(nwMinimal)
|
||||
|
||||
# Project Name
|
||||
assert theProject.setProjectName(" 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.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
|
||||
|
||||
@pytest.mark.core
|
||||
@@ -347,6 +880,7 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
|
||||
the meta line at the top of the document file.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
|
||||
assert theProject.openProject(nwLipsum)
|
||||
assert theProject.projTree["636b6aa9b697b"] is None
|
||||
assert theProject.closeProject()
|
||||
@@ -408,6 +942,10 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
|
||||
assert theProject.saveProject(nwLipsum)
|
||||
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
|
||||
|
||||
@pytest.mark.core
|
||||
@@ -418,7 +956,6 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
|
||||
contained in a single 'content' folder.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theProject.mainConf.showGUI = False
|
||||
|
||||
# Create dummy files for known legacy files
|
||||
deleteFiles = [
|
||||
@@ -451,8 +988,7 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
|
||||
# Create dummy files
|
||||
os.mkdir(os.path.join(nwOldProj, "cache"))
|
||||
for aFile in deleteFiles:
|
||||
with open(aFile, mode="w+", encoding="utf8") as outFile:
|
||||
outFile.write("Hi")
|
||||
writeFile(aFile, "Hi")
|
||||
for aFile in deleteFiles:
|
||||
assert os.path.isfile(aFile)
|
||||
|
||||
@@ -503,7 +1039,112 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
|
||||
# END Test testCoreProject_OldFormat
|
||||
|
||||
@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
|
||||
creates a backup of the Minimal test project, and then unzips the
|
||||
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)
|
||||
|
||||
# Test faulty settings
|
||||
|
||||
# No project
|
||||
dummyGUI.hasProject = False
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
dummyGUI.hasProject = True
|
||||
|
||||
# Invalid path
|
||||
theProject.mainConf.backupPath = None
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
@@ -531,9 +1178,21 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
|
||||
theProject.mainConf.backupPath = nwMinimal
|
||||
assert not theProject.zipIt(doNotify=False)
|
||||
|
||||
# Test correct settings
|
||||
# Set a valid folder
|
||||
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"))
|
||||
assert len(theFiles) == 1
|
||||
@@ -548,7 +1207,8 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
|
||||
|
||||
# Check that the main project file was restored
|
||||
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
|
||||
|
||||
@@ -10,7 +10,7 @@ from lxml import etree
|
||||
from nw.core.project import NWProject, NWItem, NWTree
|
||||
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture(scope="function")
|
||||
def dummyItems(dummyGUI):
|
||||
"""Create a list of dummy items.
|
||||
"""
|
||||
@@ -353,7 +353,7 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
|
||||
b"<expanded>True</expanded></item>"
|
||||
b"<item handle=\"c000000000001\" order=\"0\" parent=\"b000000000001\">"
|
||||
b"<name>Chapter One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
|
||||
b"<exported>True</exported><layout>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"<item handle=\"c000000000002\" order=\"0\" parent=\"b000000000001\">"
|
||||
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"
|
||||
"File Name Class Layout Document Label\n"
|
||||
"-------------------------------------------------------------\n"
|
||||
f"{pathA} NOVEL UNNUMBERED Chapter One\n"
|
||||
f"{pathA} NOVEL CHAPTER Chapter One\n"
|
||||
f"{pathB} NOVEL SCENE Scene One\n"
|
||||
f"{pathC} CHARACTER NOTE Jane Doe\n"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user