Merge branch 'main' into merge_patches

This commit is contained in:
Veronica Berglyd Olsen
2022-08-17 23:21:10 +02:00
178 changed files with 17390 additions and 9704 deletions
+2 -2
View File
@@ -64,9 +64,9 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
assert theDoc.readDocument() == "### New Scene\n\n"
# Try to open a new (non-existent) file
nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL)
nHandle = theProject.tree.findRoot(nwItemClass.NOVEL)
assert nHandle is not None
xHandle = theProject.newFile("New File", nwItemClass.NOVEL, nHandle)
xHandle = theProject.newFile("New File", nHandle)
theDoc = NWDoc(theProject, xHandle)
assert bool(theDoc) is True
assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
File diff suppressed because it is too large Load Diff
+309 -56
View File
@@ -23,23 +23,30 @@ import pytest
from lxml import etree
from PyQt5.QtGui import QIcon
from novelwriter.core import NWProject
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core
def testCoreItem_Setters(mockGUI):
def testCoreItem_Setters(mockGUI, mockRnd):
"""Test all the simple setters for the NWItem class.
"""
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
importKeys = ["i000004", "i000005", "i000006", "i000007"]
# Name
theItem.setName("A Name")
assert theItem.itemName == "A Name"
theItem.setName("\t A Name ")
assert theItem.itemName == "A Name"
theItem.setName("\t A\t\u2009\u202f\u2002\u2003\u2028\u2029Name ")
assert theItem.itemName == "A Name"
theItem.setName(123)
assert theItem.itemName == ""
@@ -65,6 +72,18 @@ def testCoreItem_Setters(mockGUI):
theItem.setParent("0123456789abc")
assert theItem.itemParent == "0123456789abc"
# Root
theItem.setRoot(None)
assert theItem.itemRoot is None
theItem.setRoot(123)
assert theItem.itemRoot is None
theItem.setRoot("0123456789abcdef")
assert theItem.itemRoot is None
theItem.setRoot("0123456789abg")
assert theItem.itemRoot is None
theItem.setRoot("0123456789abc")
assert theItem.itemRoot == "0123456789abc"
# Order
theItem.setOrder(None)
assert theItem.itemOrder == 0
@@ -74,29 +93,33 @@ def testCoreItem_Setters(mockGUI):
assert theItem.itemOrder == 1
# Importance
theItem.setStatus("Nonsense")
assert theItem.itemStatus == "New"
theItem.setStatus("New")
assert theItem.itemStatus == "New"
theItem.setStatus("Minor")
assert theItem.itemStatus == "Minor"
theItem.setStatus("Major")
assert theItem.itemStatus == "Major"
theItem.setStatus("Main")
assert theItem.itemStatus == "Main"
theItem._class = nwItemClass.CHARACTER
theItem.setImport("Word")
assert theItem.itemImport == importKeys[0] # Default
for key in importKeys:
theItem.setImport(key)
assert theItem.itemImport == key
# Status
theItem._class = nwItemClass.NOVEL
theItem.setStatus("Nonsense")
assert theItem.itemStatus == "New"
theItem.setStatus("New")
assert theItem.itemStatus == "New"
theItem.setStatus("Note")
assert theItem.itemStatus == "Note"
theItem.setStatus("Draft")
assert theItem.itemStatus == "Draft"
theItem.setStatus("Finished")
assert theItem.itemStatus == "Finished"
theItem.setStatus("Word")
assert theItem.itemStatus == statusKeys[0] # Default
for key in statusKeys:
theItem.setStatus(key)
assert theItem.itemStatus == key
# Status/Importance Wrapper
theItem._class = nwItemClass.CHARACTER
for key in importKeys:
theItem.setImport(key)
assert theItem.itemImport == key
assert theItem.itemStatus == statusKeys[3] # Should not change
theItem._class = nwItemClass.NOVEL
for key in statusKeys:
theItem.setStatus(key)
assert theItem.itemImport == importKeys[3] # Should not change
assert theItem.itemStatus == key
# Expanded
theItem.setExpanded(8)
@@ -196,6 +219,31 @@ def testCoreItem_Methods(mockGUI):
theItem.setLayout("NOTE")
assert theItem.describeMe() == "Project Note"
# Status + Icon
# =============
theItem.setType("FILE")
theItem.setStatus("Note")
theItem.setImport("Minor")
theItem.setClass("NOVEL")
stT, stI = theItem.getImportStatus()
assert stT == "Note"
assert isinstance(stI, QIcon)
theItem.setImportStatus("Draft")
stT, stI = theItem.getImportStatus()
assert stT == "Draft"
theItem.setClass("CHARACTER")
stT, stI = theItem.getImportStatus()
assert stT == "Minor"
assert isinstance(stI, QIcon)
theItem.setImportStatus("Major")
stT, stI = theItem.getImportStatus()
assert stT == "Major"
# Representation
# ==============
@@ -235,8 +283,8 @@ def testCoreItem_TypeSetter(mockGUI):
assert theItem.itemType == nwItemType.FOLDER
theItem.setType("FILE")
assert theItem.itemType == nwItemType.FILE
theItem.setType("TRASH")
assert theItem.itemType == nwItemType.TRASH
# Alternative
theItem.setType(nwItemType.ROOT)
assert theItem.itemType == nwItemType.ROOT
@@ -256,28 +304,74 @@ def testCoreItem_ClassSetter(mockGUI):
assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClass("NONSENSE")
assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClass("NO_CLASS")
assert theItem.itemClass == nwItemClass.NO_CLASS
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is True
theItem.setClass("NOVEL")
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.isNovelLike() is True
assert theItem.documentAllowed() is True
assert theItem.isInactive() is False
theItem.setClass("PLOT")
assert theItem.itemClass == nwItemClass.PLOT
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("CHARACTER")
assert theItem.itemClass == nwItemClass.CHARACTER
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("WORLD")
assert theItem.itemClass == nwItemClass.WORLD
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("TIMELINE")
assert theItem.itemClass == nwItemClass.TIMELINE
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("OBJECT")
assert theItem.itemClass == nwItemClass.OBJECT
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("ENTITY")
assert theItem.itemClass == nwItemClass.ENTITY
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("CUSTOM")
assert theItem.itemClass == nwItemClass.CUSTOM
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("ARCHIVE")
assert theItem.itemClass == nwItemClass.ARCHIVE
assert theItem.isNovelLike() is True
assert theItem.documentAllowed() is True
assert theItem.isInactive() is True
theItem.setClass("TRASH")
assert theItem.itemClass == nwItemClass.TRASH
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is True
assert theItem.isInactive() is True
# Alternative
theItem.setClass(nwItemClass.NOVEL)
assert theItem.itemClass == nwItemClass.NOVEL
@@ -306,23 +400,7 @@ def testCoreItem_LayoutSetter(mockGUI):
theItem.setLayout("NOTE")
assert theItem.itemLayout == nwItemLayout.NOTE
# Deprecated Layouts
theItem.setLayout("TITLE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PAGE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("BOOK")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PARTITION")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("UNNUMBERED")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("CHAPTER")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("SCENE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
# Alternatives
# Alternative
theItem.setLayout(nwItemLayout.NOTE)
assert theItem.itemLayout == nwItemLayout.NOTE
@@ -330,23 +408,83 @@ def testCoreItem_LayoutSetter(mockGUI):
@pytest.mark.core
def testCoreItem_XMLPackUnpack(mockGUI, caplog):
def testCoreItem_ClassDefaults(mockGUI):
"""Test the setter for the default values.
"""
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
# Root items should not have their class updated
theItem.setParent(None)
theItem.setClass(nwItemClass.NO_CLASS)
assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClassDefaults(nwItemClass.NOVEL)
assert theItem.itemClass == nwItemClass.NO_CLASS
# Non-root items should have their class updated
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClassDefaults(nwItemClass.NOVEL)
assert theItem.itemClass == nwItemClass.NOVEL
# Non-layout items should have their layout set based on class
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
theItem.setLayout(nwItemLayout.NO_LAYOUT)
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
theItem.setClassDefaults(nwItemClass.NOVEL)
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
theItem.setLayout(nwItemLayout.NO_LAYOUT)
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
theItem.setClassDefaults(nwItemClass.PLOT)
assert theItem.itemLayout == nwItemLayout.NOTE
# If documents are not allowed in that class, the layout should be changed
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
theItem.setLayout(nwItemLayout.DOCUMENT)
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setClassDefaults(nwItemClass.PLOT)
assert theItem.itemLayout == nwItemLayout.NOTE
# In all cases, status and importance should no longer be None
assert theItem.itemStatus is not None
assert theItem.itemImport is not None
# END Test testCoreItem_ClassDefaults
@pytest.mark.core
def testCoreItem_XMLPackUnpack(mockGUI, caplog, mockRnd):
"""Test packing and unpacking XML objects for the NWItem class.
"""
theProject = NWProject(mockGUI)
nwXML = etree.Element("novelWriterXML")
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
importKeys = ["i000004", "i000005", "i000006", "i000007"]
# File
# ====
theItem = NWItem(theProject)
theItem.setHandle("0123456789abc")
theItem.setParent("0123456789abc")
theItem.setRoot("0123456789abc")
theItem.setOrder(1)
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FILE")
theItem.setStatus("Main")
theItem.setImport(importKeys[3])
theItem.setLayout("NOTE")
theItem.setExported(False)
theItem.setParaCount(3)
@@ -358,19 +496,20 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
xContent = etree.SubElement(nwXML, "content")
theItem.packXML(xContent)
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b"<content>"
b"<item handle=\"0123456789abc\" order=\"1\" parent=\"0123456789abc\">"
b"<name>A Name</name><type>FILE</type><class>NOVEL</class><status>New</status>"
b"<exported>False</exported><layout>NOTE</layout><charCount>7</charCount>"
b"<wordCount>5</wordCount><paraCount>3</paraCount><cursorPos>11</cursorPos></item>"
b"</content>"
)
b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FILE" class="NOVEL" layout="NOTE"><meta expanded="False" charCount="7" '
b'wordCount="5" paraCount="3" cursorPos="11"/><name status="None" import="%s" '
b'exported="False">A Name</name></item>'
b'</content>'
) % bytes(importKeys[3], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1
assert theItem.isExported is False
assert theItem.paraCount == 3
@@ -380,6 +519,8 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE
assert theItem.itemStatus == statusKeys[0] # Was None, should now be default
assert theItem.itemImport == importKeys[3]
# Folder
# ======
@@ -387,11 +528,12 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
theItem = NWItem(theProject)
theItem.setHandle("0123456789abc")
theItem.setParent("0123456789abc")
theItem.setRoot("0123456789abc")
theItem.setOrder(1)
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FOLDER")
theItem.setStatus("Main")
theItem.setStatus(statusKeys[1])
theItem.setLayout("NOTE")
theItem.setExpanded(True)
theItem.setExported(False)
@@ -404,18 +546,19 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
xContent = etree.SubElement(nwXML, "content")
theItem.packXML(xContent)
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b"<content>"
b"<item handle=\"0123456789abc\" order=\"1\" parent=\"0123456789abc\">"
b"<name>A Name</name><type>FOLDER</type><class>NOVEL</class><status>New</status>"
b"<expanded>True</expanded></item>"
b"</content>"
)
b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="%s" '
b'import="None">A Name</name></item>'
b'</content>'
) % bytes(statusKeys[1], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1
assert theItem.isExpanded is True
assert theItem.isExported is True
@@ -426,6 +569,8 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemStatus == statusKeys[1]
assert theItem.itemImport == importKeys[0] # Was None, should now be default
# Errors
# ======
@@ -462,3 +607,111 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
)
# END Test testCoreItem_XMLPackUnpack
@pytest.mark.core
def testCoreItem_ConvertFromFmt12(mockGUI):
"""Test the setter for all the nwItemLayout values for the NWItem
class using the class names that were present in file format 1.2.
"""
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
# Deprecated Layouts
theItem.setLayout("TITLE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PAGE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("BOOK")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PARTITION")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("UNNUMBERED")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("CHAPTER")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("SCENE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("MUMBOJUMBO")
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# END Test testCoreItem_ConvertFromFmt12
@pytest.mark.core
def testCoreItem_ConvertFromFmt13(mockGUI):
"""Test packing and unpacking XML objects for the NWItem class from
format version 1.3
"""
theProject = NWProject(mockGUI)
# Make Version 1.3 XML
nwXML = etree.Element("novelWriterXML")
xContent = etree.SubElement(nwXML, "content")
# Folder
xPack = etree.SubElement(xContent, "item", attrib={
"handle": "a000000000001",
"order": "1",
"parent": "b000000000001",
})
NWItem._subPack(xPack, "name", text="Folder")
NWItem._subPack(xPack, "type", text="FOLDER")
NWItem._subPack(xPack, "class", text="NOVEL")
NWItem._subPack(xPack, "status", text="New")
NWItem._subPack(xPack, "expanded", text="True")
# Unpack Folder
theItem = NWItem(theProject)
theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "a000000000001"
assert theItem.itemParent == "b000000000001"
assert theItem.itemOrder == 1
assert theItem.isExpanded is True
assert theItem.isExported is True
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# File
xPack = etree.SubElement(xContent, "item", attrib={
"handle": "c000000000001",
"order": "2",
"parent": "a000000000001",
})
NWItem._subPack(xPack, "name", text="Scene")
NWItem._subPack(xPack, "type", text="FILE")
NWItem._subPack(xPack, "class", text="NOVEL")
NWItem._subPack(xPack, "status", text="New")
NWItem._subPack(xPack, "exported", text="True")
NWItem._subPack(xPack, "layout", text="DOCUMENT")
NWItem._subPack(xPack, "charCount", text="600")
NWItem._subPack(xPack, "wordCount", text="100")
NWItem._subPack(xPack, "paraCount", text="6")
NWItem._subPack(xPack, "cursorPos", text="50")
# Unpack File
theItem = NWItem(theProject)
theItem.unpackXML(xContent[1])
assert theItem.itemHandle == "c000000000001"
assert theItem.itemParent == "a000000000001"
assert theItem.itemOrder == 2
assert theItem.isExpanded is False
assert theItem.isExported is True
assert theItem.charCount == 600
assert theItem.wordCount == 100
assert theItem.paraCount == 6
assert theItem.cursorPos == 50
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.DOCUMENT
# Deprecated Type
theItem.setType("TRASH")
assert theItem.itemType == nwItemType.ROOT
# END Test testCoreItem_ConvertFromFmt13
+268 -217
View File
@@ -19,24 +19,28 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
import pytest
from shutil import copyfile
from zipfile import ZipFile
from lxml import etree
from tools import cmpFiles, writeFile, readFile
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE
from mock import causeOSError
from novelwriter.core.project import NWProject
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwFiles
from novelwriter.core.tree import NWTree
from novelwriter.core.index import NWIndex
from novelwriter.core.project import NWProject
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
@pytest.mark.core
def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary. With
default setting, creating a Minimal project.
"""
@@ -45,7 +49,6 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx")
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
# Setting no data should fail
assert theProject.newProject({}) is False
@@ -61,10 +64,6 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
# Creating the project once more should fail
assert theProject.newProject({"projPath": fncDir}) is False
# Check the new project
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
# Open again
assert theProject.openProject(projFile) is True
@@ -72,7 +71,7 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
assert theProject.saveProject() is True
assert theProject.closeProject() is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
assert theProject.projChanged is False
# Open a second time
@@ -82,13 +81,13 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
assert theProject.saveProject() is True
assert theProject.closeProject() is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# END Test testCoreProject_NewMinimal
@pytest.mark.core
def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI):
def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type with chapters and scenes.
"""
@@ -108,29 +107,25 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI):
nwItemClass.PLOT,
nwItemClass.CHARACTER,
nwItemClass.WORLD,
nwItemClass.TIMELINE,
nwItemClass.OBJECT,
nwItemClass.ENTITY,
],
"addNotes": True,
"numChapters": 3,
"numScenes": 3,
"chFolders": True,
}
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
assert theProject.newProject(projData) is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# END Test testCoreProject_NewCustomA
@pytest.mark.core
def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI):
def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type without chapters, but with scenes.
"""
@@ -150,23 +145,19 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI):
nwItemClass.PLOT,
nwItemClass.CHARACTER,
nwItemClass.WORLD,
nwItemClass.TIMELINE,
nwItemClass.OBJECT,
nwItemClass.ENTITY,
],
"addNotes": True,
"numChapters": 0,
"numScenes": 6,
"chFolders": True,
}
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
assert theProject.newProject(projData) is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# END Test testCoreProject_NewCustomB
@@ -186,7 +177,6 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir):
"popCustom": False,
}
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
# Sample set, but no path
assert not theProject.newProject({"popSample": True})
@@ -235,7 +225,6 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir):
"popCustom": False,
}
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
# Make sure we do not pick up the novelwriter/assets/sample.zip file
tmpConf.assetPath = tmpDir
@@ -259,7 +248,7 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir):
@pytest.mark.core
def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Check that new root folders can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
@@ -267,62 +256,84 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx")
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
buildTestProject(theProject, fncDir)
assert theProject.newProject({"projPath": fncDir}) is True
assert theProject.setProjectPath(fncDir) is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
assert theProject.openProject(projFile) is True
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 isinstance(theProject.newRoot(nwItemClass.NOVEL), str)
assert isinstance(theProject.newRoot(nwItemClass.PLOT), str)
assert isinstance(theProject.newRoot(nwItemClass.CHARACTER), str)
assert isinstance(theProject.newRoot(nwItemClass.WORLD), str)
assert isinstance(theProject.newRoot(nwItemClass.TIMELINE), str)
assert isinstance(theProject.newRoot(nwItemClass.OBJECT), str)
assert isinstance(theProject.newRoot(nwItemClass.CUSTOM), str)
assert isinstance(theProject.newRoot(nwItemClass.CUSTOM), str)
assert theProject.projChanged is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
assert theProject.projChanged is False
# END Test testCoreProject_NewRoot
@pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI):
def testCoreProject_NewFileFolder(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Check that new files can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFileFolder_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFileFolder_nwProject.nwx")
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
buildTestProject(theProject, fncDir)
assert theProject.newProject({"projPath": fncDir}) is True
assert theProject.setProjectPath(fncDir) is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
assert theProject.openProject(projFile) is True
assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str)
assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str)
assert theProject.projChanged
# Invalid call
assert theProject.newFolder("New Folder", "1234567890abc") is None
assert theProject.newFile("New File", "1234567890abc") is None
# Add files properly
assert theProject.newFolder("Stuff", "0000000000015") == "0000000000028"
assert theProject.newFile("Hello", "0000000000015") == "0000000000029"
assert theProject.newFile("Jane", "0000000000012") == "000000000002a"
assert "0000000000028" in theProject.tree
assert "0000000000029" in theProject.tree
assert "000000000002a" in theProject.tree
# Write to file, failed
assert theProject.writeNewFile("blabla", 1, True) is False # Not a handle
assert theProject.writeNewFile("0000000000028", 1, True) is False # Not a file
assert theProject.writeNewFile("0000000000014", 1, True) is False # Already has content
# Write to file, success
assert theProject.writeNewFile("0000000000029", 2, True) is True
assert NWDoc(theProject, "0000000000029").readDocument() == "## Hello\n\n"
assert theProject.writeNewFile("000000000002a", 1, False) is True
assert NWDoc(theProject, "000000000002a").readDocument() == "# Jane\n\n"
# Save, close and check
assert theProject.projChanged is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
assert theProject.projChanged is False
# END Test testCoreProject_NewFile
# END Test testCoreProject_NewFileFolder
@pytest.mark.core
@@ -452,12 +463,15 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
os.rename(oName, rName)
# Add some legacy stuff that cannot be removed
writeFile(os.path.join(nwMinimal, "junk"), "stuff")
os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "junk"), "stuff")
mockGUI.clear()
assert theProject.openProject(nwMinimal) is True
assert "data_0" in mockGUI.lastAlert
with monkeypatch.context() as mp:
mp.setattr(theProject, "_legacyDataFolder", causeOSError)
os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.nwd"), "stuff")
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff")
mockGUI.clear()
assert theProject.openProject(nwMinimal) is True
assert "There was an error updating the project." in mockGUI.lastAlert
assert theProject.closeProject()
# END Test testCoreProject_Open
@@ -500,7 +514,7 @@ def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir):
assert theProject.saveProject() is True
assert theProject.saveCount == saveCount + 1
assert theProject.autoCount == autoCount
assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Check that a second save creates a .bak file
assert os.path.isfile(backFile) is True
@@ -511,7 +525,7 @@ def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir):
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])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Close test project
assert theProject.closeProject()
@@ -630,6 +644,11 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal)
# Storage Objects
assert isinstance(theProject.index, NWIndex)
assert isinstance(theProject.tree, NWTree)
assert isinstance(theProject.options, OptionState)
# Move Novel ROOT to after its files
oldOrder = [
"a508bb932959c", # ROOT: Novel
@@ -651,17 +670,17 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
]
assert theProject.projTree.handles() == oldOrder
assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
assert theProject.projTree.handles() == newOrder
assert theProject.tree.handles() == newOrder
# Add a non-existing item
theProject.projTree._treeOrder.append("01234567789abc")
theProject.tree._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"
nHandle = theProject.newFile("Test File", "a6d311a93600a")
theProject.tree[nHandle].setParent("cba9876543210")
assert theProject.tree[nHandle].itemParent == "cba9876543210"
retOrder = []
for tItem in theProject.getProjectItems():
@@ -678,19 +697,138 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
]
assert theProject.projTree[nHandle].itemParent is None
assert theProject.tree[nHandle].itemParent is None
# END Test testCoreProject_AccessItems
@pytest.mark.core
def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
"""Test the status and importance flag handling.
"""
theProject = NWProject(mockGUI)
buildTestProject(theProject, fncDir)
statusKeys = ["s000008", "s000009", "s00000a", "s00000b"]
importKeys = ["i00000c", "i00000d", "i00000e", "i00000f"]
# Change Status
# =============
theProject.tree["0000000000014"].setStatus("Finished")
theProject.tree["0000000000015"].setStatus("Draft")
theProject.tree["0000000000016"].setStatus("Note")
theProject.tree["0000000000017"].setStatus("Finished")
assert theProject.tree["0000000000014"].itemStatus == statusKeys[3]
assert theProject.tree["0000000000015"].itemStatus == statusKeys[2]
assert theProject.tree["0000000000016"].itemStatus == statusKeys[1]
assert theProject.tree["0000000000017"].itemStatus == statusKeys[3]
newList = [
{"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)},
{"key": statusKeys[1], "name": "Draft", "cols": (2, 2, 2)}, # These are swapped
{"key": statusKeys[2], "name": "Note", "cols": (3, 3, 3)}, # These are swapped
{"key": statusKeys[3], "name": "Edited", "cols": (4, 4, 4)}, # Renamed
{"key": None, "name": "Finished", "cols": (5, 5, 5)}, # New, reused name
]
assert theProject.setStatusColours(None, None) is False
assert theProject.setStatusColours([], []) is False
assert theProject.setStatusColours(newList, []) is True
assert theProject.statusItems.name(statusKeys[0]) == "New"
assert theProject.statusItems.name(statusKeys[1]) == "Draft"
assert theProject.statusItems.name(statusKeys[2]) == "Note"
assert theProject.statusItems.name(statusKeys[3]) == "Edited"
assert theProject.statusItems.cols(statusKeys[0]) == (1, 1, 1)
assert theProject.statusItems.cols(statusKeys[1]) == (2, 2, 2)
assert theProject.statusItems.cols(statusKeys[2]) == (3, 3, 3)
assert theProject.statusItems.cols(statusKeys[3]) == (4, 4, 4)
# Check the new entry
lastKey = theProject.statusItems.check("Finished")
assert lastKey == "s000018"
assert theProject.statusItems.name(lastKey) == "Finished"
assert theProject.statusItems.cols(lastKey) == (5, 5, 5)
# Delete last entry
assert theProject.setStatusColours([], [lastKey]) is True
assert theProject.statusItems.name(lastKey) == "New"
# Change Importance
# =================
fHandle = theProject.newFile("Jane Doe", "0000000000012")
theProject.tree[fHandle].setImport("Main")
assert theProject.tree[fHandle].itemImport == importKeys[3]
newList = [
{"key": importKeys[0], "name": "New", "cols": (1, 1, 1)},
{"key": importKeys[1], "name": "Minor", "cols": (2, 2, 2)},
{"key": importKeys[2], "name": "Major", "cols": (3, 3, 3)},
{"key": importKeys[3], "name": "Min", "cols": (4, 4, 4)},
{"key": None, "name": "Max", "cols": (5, 5, 5)},
]
assert theProject.setImportColours(None, None) is False
assert theProject.setImportColours([], []) is False
assert theProject.setImportColours(newList, []) is True
assert theProject.importItems.name(importKeys[0]) == "New"
assert theProject.importItems.name(importKeys[1]) == "Minor"
assert theProject.importItems.name(importKeys[2]) == "Major"
assert theProject.importItems.name(importKeys[3]) == "Min"
assert theProject.importItems.cols(importKeys[0]) == (1, 1, 1)
assert theProject.importItems.cols(importKeys[1]) == (2, 2, 2)
assert theProject.importItems.cols(importKeys[2]) == (3, 3, 3)
assert theProject.importItems.cols(importKeys[3]) == (4, 4, 4)
# Check the new entry
lastKey = theProject.importItems.check("Max")
assert lastKey == "i00001a"
assert theProject.importItems.name(lastKey) == "Max"
assert theProject.importItems.cols(lastKey) == (5, 5, 5)
# Delete last entry
assert theProject.setImportColours([], [lastKey]) is True
assert theProject.importItems.name(lastKey) == "New"
# Delete Status/Import
# ====================
theProject.statusItems.resetCounts()
for key in list(theProject.statusItems.keys()):
assert theProject.statusItems.remove(key) is True
theProject.importItems.resetCounts()
for key in list(theProject.importItems.keys()):
assert theProject.importItems.remove(key) is True
assert len(theProject.statusItems) == 0
assert len(theProject.importItems) == 0
assert theProject.saveProject() is True
assert theProject.closeProject() is True
# This should restore the default status/import labels
assert theProject.openProject(fncDir) is True
assert theProject.saveProject() is True
assert theProject.statusItems.name("s000023") == "New"
assert theProject.statusItems.name("s000024") == "Note"
assert theProject.statusItems.name("s000025") == "Draft"
assert theProject.statusItems.name("s000026") == "Finished"
assert theProject.importItems.name("i000027") == "New"
assert theProject.importItems.name("i000028") == "Minor"
assert theProject.importItems.name("i000029") == "Major"
assert theProject.importItems.name("i00002a") == "Main"
# END Test testCoreProject_StatusImport
@pytest.mark.core
def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
"""Test other project class methods and functions.
"""
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal)
assert theProject.projPath == nwMinimal
buildTestProject(theProject, fncDir)
# Setting project path
assert theProject.setProjectPath(None)
@@ -701,16 +839,16 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.projPath == os.path.expanduser("~")
# Create a new folder and populate it
projPath = os.path.join(nwMinimal, "mock1")
projPath = os.path.join(fncDir, "mock1")
assert theProject.setProjectPath(projPath, newProject=True)
# Make os.mkdir fail
monkeypatch.setattr("os.mkdir", causeOSError)
projPath = os.path.join(nwMinimal, "mock2")
projPath = os.path.join(fncDir, "mock2")
assert not theProject.setProjectPath(projPath, newProject=True)
# Set back
assert theProject.setProjectPath(nwMinimal)
assert theProject.setProjectPath(fncDir)
# Project Name
assert theProject.setProjectName(" A Name ")
@@ -748,9 +886,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# 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"
hTrash = "0000000000018"
assert theProject.tree[hTrash] is None
assert theProject.trashFolder() == hTrash
assert theProject.trashFolder() == hTrash
# Project backup
assert theProject.doBackup is True
@@ -774,9 +913,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Spell language
theProject.projChanged = False
assert theProject.setSpellLang(None)
assert theProject.projSpell is None
assert theProject.setSpellLang("None")
assert theProject.setSpellLang(None) is False
assert theProject.projSpell is None
assert theProject.setSpellLang("None") is False # Should be interpreded as None
assert theProject.projSpell is None
assert theProject.setSpellLang("en_GB")
assert theProject.projSpell == "en_GB"
@@ -790,11 +930,9 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.setProjectLang("en_GB") is True
assert theProject.projLang == "en_GB"
# Automatic outline update
theProject.projChanged = False
assert theProject.setAutoOutline(True)
assert not theProject.setAutoOutline(False)
assert theProject.projChanged
# Language Lookup
assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten"
# Last edited
theProject.projChanged = False
@@ -816,70 +954,20 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Change project tree order
oldOrder = [
"a508bb932959c", "a35baf2e93843", "a6d311a93600a",
"f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
"afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568",
"0000000000010", "0000000000011", "0000000000012",
"0000000000013", "0000000000014", "0000000000015",
"0000000000016", "0000000000017", "0000000000018",
]
newOrder = [
"f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
"a508bb932959c", "a35baf2e93843", "a6d311a93600a",
"afb3043c7b2b3", "9d5247ab588e0",
"0000000000013", "0000000000014", "0000000000015",
"0000000000010", "0000000000011", "0000000000012",
"0000000000016", "0000000000017",
]
assert theProject.projTree.handles() == oldOrder
assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
assert theProject.projTree.handles() == newOrder
assert theProject.tree.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]
assert theProject.tree.handles() == oldOrder
# Session stats
theProject.currWCount = 200
@@ -894,7 +982,7 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert not theProject._appendSessionStats(idleTime=0)
# Write entry
assert theProject.projMeta == os.path.join(nwMinimal, "meta")
assert theProject.projMeta == os.path.join(fncDir, "meta")
statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000
@@ -948,9 +1036,17 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
"""
theProject = NWProject(mockGUI)
assert theProject.openProject(nwLipsum)
assert theProject.projTree["636b6aa9b697b"] is None
assert theProject.closeProject()
assert theProject.openProject(nwLipsum) is True
assert theProject.tree["636b6aa9b697b"] is None
# Add a file with non-existent parent
# This file will be renoved from the project on open
oHandle = theProject.newFile("Oops", "b3643d0f92e32")
theProject.tree[oHandle].setParent("1234567890abc")
# Save and close
assert theProject.saveProject() is True
assert theProject.closeProject() is True
# First Item with Meta Data
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
@@ -980,11 +1076,11 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
assert theProject.openProject(nwLipsum)
assert theProject.projPath is not None
assert theProject.projTree["636b6aa9b697bb"] is None
assert theProject.projTree["abcdefghijklm"] is None
assert theProject.tree["636b6aa9b697bb"] is None
assert theProject.tree["abcdefghijklm"] is None
# First Item with Meta Data
oItem = theProject.projTree["636b6aa9b697b"]
oItem = theProject.tree["636b6aa9b697b"]
assert oItem is not None
assert oItem.itemName == "[Recovered] Mars"
assert oItem.itemHandle == "636b6aa9b697b"
@@ -994,7 +1090,7 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
assert oItem.itemLayout == nwItemLayout.NOTE
# Second Item without Meta Data
oItem = theProject.projTree["736b6aa9b697b"]
oItem = theProject.tree["736b6aa9b697b"]
assert oItem is not None
assert oItem.itemName == "Recovered File 1"
assert oItem.itemHandle == "736b6aa9b697b"
@@ -1042,14 +1138,6 @@ def testCoreProject_OldFormat(mockGUI, nwOldProj):
os.path.join(nwOldProj, "meta", "sessionLogOptions.json"),
]
# Add some files that shouldn't be there
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.nwd"))
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.txt"))
# Add some folders that shouldn't be there
os.mkdir(os.path.join(nwOldProj, "stuff"))
os.mkdir(os.path.join(nwOldProj, "data_1", "stuff"))
# Create mock files
os.mkdir(os.path.join(nwOldProj, "cache"))
for aFile in deleteFiles:
@@ -1063,7 +1151,6 @@ def testCoreProject_OldFormat(mockGUI, nwOldProj):
for aFile in deleteFiles:
assert not os.path.isfile(aFile)
assert not os.path.isdir(os.path.join(nwOldProj, "data_1", "stuff"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_1"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_7"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_8"))
@@ -1071,12 +1158,6 @@ def testCoreProject_OldFormat(mockGUI, nwOldProj):
assert not os.path.isdir(os.path.join(nwOldProj, "data_a"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_f"))
# Check stuff that has been moved
assert os.path.isdir(os.path.join(nwOldProj, "junk"))
assert os.path.isdir(os.path.join(nwOldProj, "junk", "stuff"))
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.nwd"))
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.txt"))
# Check that files we want to keep are in the right place
assert os.path.isdir(os.path.join(nwOldProj, "cache"))
assert os.path.isdir(os.path.join(nwOldProj, "content"))
@@ -1111,10 +1192,6 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
theProject = NWProject(mockGUI)
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, "stuff")
@@ -1122,7 +1199,7 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
with monkeypatch.context() as mp:
mp.setattr("os.unlink", causeOSError)
assert not theProject._deprecatedFiles()
assert theProject._deprecatedFiles() is False
assert theProject._deprecatedFiles()
assert not os.path.isfile(tstFile)
@@ -1131,63 +1208,36 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
tstFile = os.path.join(fncDir, "data_0")
writeFile(tstFile, "stuff")
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
with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
# This causes a failure to move 'stuff' to 'junk'
with monkeypatch.context() as mp:
mp.setattr("os.rename", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
# This should be successful
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) == 0
assert os.path.isdir(os.path.join(fncDir, "junk", "stuff"))
assert theProject._legacyDataFolder(tstFile) is False
# 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")
tstData2 = os.path.join(fncDir, "data_2")
tstData3 = os.path.join(fncDir, "data_3")
tstDoc1m = os.path.join(tstData2, "000000000001_main.nwd")
tstDoc1b = os.path.join(tstData2, "000000000001_main.bak")
tstDoc2m = os.path.join(tstData2, "000000000002_main.nwd")
tstDoc2b = os.path.join(tstData2, "000000000002_main.bak")
tstDoc3m = os.path.join(tstData3, "tooshort003_main.nwd")
tstDoc3b = os.path.join(tstData3, "tooshort003_main.bak")
tstDir4a = os.path.join(tstData3, "stuff")
os.mkdir(tstData)
os.mkdir(tstData2)
os.mkdir(tstData3)
writeFile(tstDoc1m, "stuff")
writeFile(tstDoc1b, "stuff")
writeFile(tstDoc2m, "stuff")
writeFile(tstDoc2b, "stuff")
writeFile(tstDoc3m, "stuff")
writeFile(tstDoc3b, "stuff")
os.mkdir(tstDir4a)
# Make the above fail
with monkeypatch.context() as mp:
mp.setattr("os.rename", causeOSError)
mp.setattr("os.unlink", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
with pytest.raises(OSError):
theProject._legacyDataFolder(tstData2)
theProject._legacyDataFolder(tstData3)
assert os.path.isfile(tstDoc1m)
assert os.path.isfile(tstDoc1b)
assert os.path.isfile(tstDoc2m)
@@ -1196,15 +1246,16 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
assert os.path.isfile(tstDoc3b)
# And succeed ...
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) == 0
assert theProject._legacyDataFolder(tstData2) is True
assert theProject._legacyDataFolder(tstData3) is True
assert not os.path.isdir(tstData)
assert not os.path.isdir(tstData2)
assert os.path.isdir(tstData3)
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"))
assert os.path.isfile(os.path.join(fncDir, tstData3, "tooshort003_main.nwd"))
assert os.path.isfile(os.path.join(fncDir, tstData3, "tooshort003_main.bak"))
assert os.path.isdir(tstDir4a)
# END Test testCoreProject_LegacyData
+297 -78
View File
@@ -20,101 +20,314 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import random
from lxml import etree
from PyQt5.QtGui import QIcon
from novelwriter.core.status import NWStatus
statusKeys = ["sa3b179", "s1c8031", "s06671a", "sbdd640"]
importKeys = ["i466852", "i3eb13b", "i392456", "i23b8c1"]
@pytest.mark.core
def testCoreStatus_Internal():
"""Test all the internal functions of the NWStatus class.
"""
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theImport = NWStatus(NWStatus.IMPORT)
with pytest.raises(Exception):
NWStatus(999)
# Generate Key
# ============
assert theStatus._newKey() == statusKeys[0]
assert theStatus._newKey() == statusKeys[1]
# Key collision, should move to key 3
theStatus.write(statusKeys[2], "Crash", (0, 0, 0))
assert theStatus._newKey() == statusKeys[3]
assert theImport._newKey() == importKeys[0]
assert theImport._newKey() == importKeys[1]
# Key collision, should move to key 3
theImport.write(importKeys[2], "Crash", (0, 0, 0))
assert theImport._newKey() == importKeys[3]
# Check Key
# =========
assert theStatus._isKey(None) is False # Not a string
assert theStatus._isKey("s00000") is False # Too short
assert theStatus._isKey("s000000") is True # Correct length
assert theStatus._isKey("s0000000") is False # Too long
assert theStatus._isKey("i000000") is False # Wrong type
assert theStatus._isKey("q000000") is False # Wrong type
assert theStatus._isKey("s12345H") is False # Not a hex value
assert theStatus._isKey("s12345F") is False # Not a lower case hex value
assert theStatus._isKey("s12345f") is True # Valid hex value
assert theImport._isKey(None) is False # Not a string
assert theImport._isKey("i00000") is False # Too short
assert theImport._isKey("i000000") is True # Correct length
assert theImport._isKey("i0000000") is False # Too long
assert theImport._isKey("s000000") is False # Wrong type
assert theImport._isKey("q000000") is False # Wrong type
assert theImport._isKey("i12345H") is False # Not a hex value
assert theImport._isKey("i12345F") is False # Not a lower case hex value
assert theImport._isKey("i12345f") is True # Valid hex value
# END Test testCoreStatus_Internal
@pytest.mark.core
def testCoreStatus_Iterator():
"""Test the iterator functions of the NWStatus class.
"""
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100))
theStatus.write(None, "Note", (200, 50, 0))
theStatus.write(None, "Draft", (200, 150, 0))
theStatus.write(None, "Finished", (50, 200, 0))
# Direct access
entry = theStatus[statusKeys[0]]
assert entry["cols"] == (100, 100, 100)
assert entry["name"] == "New"
assert entry["count"] == 0
assert isinstance(entry["icon"], QIcon)
# Iterate
entries = list(theStatus)
assert len(entries) == 4
assert len(theStatus) == 4
# Keys
assert list(theStatus.keys()) == statusKeys
# Items
for index, (key, entry) in enumerate(theStatus.items()):
assert key == statusKeys[index]
assert "cols" in entry
assert "name" in entry
assert "count" in entry
assert "icon" in entry
# Valuse
for entry in theStatus.values():
assert "cols" in entry
assert "name" in entry
assert "count" in entry
assert "icon" in entry
# END Test testCoreStatus_Iterator
@pytest.mark.core
def testCoreStatus_Entries():
"""Test all the simple setters for the NWItem class.
"""Test all the simple setters for the NWStatus class.
"""
theStatus = NWStatus()
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
# Add entries
theStatus.addEntry("New", (100, 100, 100))
theStatus.addEntry("Minor", (200, 50, 0))
theStatus.addEntry("Major", (200, 150, 0))
theStatus.addEntry("Main", (50, 200, 0))
# Write
# =====
assert theStatus._theLabels == ["New", "Minor", "Major", "Main"]
assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)]
assert theStatus._theCounts == [0, 0, 0, 0]
assert theStatus._theMap["New"] == 0
assert theStatus._theMap["Minor"] == 1
assert theStatus._theMap["Major"] == 2
assert theStatus._theMap["Main"] == 3
assert theStatus._theLength == 4
# Have a key
theStatus.write(statusKeys[0], "Entry 1", (200, 100, 50))
assert theStatus[statusKeys[0]]["name"] == "Entry 1"
assert theStatus[statusKeys[0]]["cols"] == (200, 100, 50)
# Lookups
assert theStatus.lookupEntry(None) is None
assert theStatus.lookupEntry("stuff") is None
assert theStatus.lookupEntry("Main") == 3
# Don't have a key
theStatus.write(None, "Entry 2", (210, 110, 60))
assert theStatus[statusKeys[1]]["name"] == "Entry 2"
assert theStatus[statusKeys[1]]["cols"] == (210, 110, 60)
# Checks
assert theStatus.checkEntry(123) == "New"
assert theStatus.checkEntry("Stuff") == "New"
assert theStatus.checkEntry("New ") == "New"
assert theStatus.checkEntry(" Main ") == "Main"
# Wrong colour spec
theStatus.write(None, "Entry 3", "what?")
assert theStatus[statusKeys[2]]["name"] == "Entry 3"
assert theStatus[statusKeys[2]]["cols"] == (100, 100, 100)
# Set new list
newList = [
("New", 1, 1, 1, "New"),
("Minor", 2, 2, 2, "Minor"),
("Major", 3, 3, 3, "Major"),
("Min", 4, 4, 4, "Main"),
("Max", 5, 5, 5, None),
]
assert theStatus.setNewEntries(None) == {}
assert theStatus.setNewEntries(newList) == {"Main": "Min"}
# Wrong colour count
theStatus.write(None, "Entry 4", (10, 20))
assert theStatus[statusKeys[3]]["name"] == "Entry 4"
assert theStatus[statusKeys[3]]["cols"] == (100, 100, 100)
assert theStatus._theLabels == ["New", "Minor", "Major", "Min", "Max"]
assert theStatus._theColours == [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)]
assert theStatus._theCounts == [0, 0, 0, 0, 0]
assert theStatus._theMap["New"] == 0
assert theStatus._theMap["Minor"] == 1
assert theStatus._theMap["Major"] == 2
assert theStatus._theMap["Min"] == 3
assert theStatus._theMap["Max"] == 4
assert theStatus._theLength == 5
# Check reverse map
assert theStatus._reverse == {
"Entry 1": statusKeys[0],
"Entry 2": statusKeys[1],
"Entry 3": statusKeys[2],
"Entry 4": statusKeys[3],
}
# Add counts
countTo = [3, 5, 7, 9, 11]
# Check
# =====
# Normal lookup
for key in statusKeys:
assert theStatus.check(key) == key
# Reverse map lookup
assert theStatus.check("Entry 1") == statusKeys[0]
assert theStatus.check("Entry 2") == statusKeys[1]
assert theStatus.check("Entry 3") == statusKeys[2]
assert theStatus.check("Entry 4") == statusKeys[3]
# Non-existing name
assert theStatus.check("Entry 5") == statusKeys[0]
# Name Access
# ===========
assert theStatus.name(statusKeys[0]) == "Entry 1"
assert theStatus.name(statusKeys[1]) == "Entry 2"
assert theStatus.name(statusKeys[2]) == "Entry 3"
assert theStatus.name(statusKeys[3]) == "Entry 4"
assert theStatus.name("blablabla") == "Entry 1"
# Colour Access
# =============
assert theStatus.cols(statusKeys[0]) == (200, 100, 50)
assert theStatus.cols(statusKeys[1]) == (210, 110, 60)
assert theStatus.cols(statusKeys[2]) == (100, 100, 100)
assert theStatus.cols(statusKeys[3]) == (100, 100, 100)
assert theStatus.cols("blablabla") == (200, 100, 50)
# Icon Access
# ===========
assert isinstance(theStatus.icon(statusKeys[0]), QIcon)
assert isinstance(theStatus.icon(statusKeys[1]), QIcon)
assert isinstance(theStatus.icon(statusKeys[2]), QIcon)
assert isinstance(theStatus.icon(statusKeys[3]), QIcon)
assert isinstance(theStatus.icon("blablabla"), QIcon)
# Increment and Count Access
# ==========================
countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo):
for _ in range(n):
theStatus.countEntry(theStatus._theLabels[i])
assert theStatus._theCounts == countTo
theStatus.increment(statusKeys[i])
# Iterate
for i, (sA, sB, sC) in enumerate(theStatus):
assert sA == theStatus._theLabels[i]
assert sB == theStatus._theColours[i]
assert sC == theStatus._theCounts[i]
assert theStatus.count(statusKeys[0]) == countTo[0]
assert theStatus.count(statusKeys[1]) == countTo[1]
assert theStatus.count(statusKeys[2]) == countTo[2]
assert theStatus.count(statusKeys[3]) == countTo[3]
assert theStatus.count("blablabla") == countTo[0]
assert theStatus[9] == (None, None, None)
# Clear counts
theStatus.resetCounts()
assert theStatus._theCounts == [0, 0, 0, 0, 0]
assert theStatus.count(statusKeys[0]) == 0
assert theStatus.count(statusKeys[1]) == 0
assert theStatus.count(statusKeys[2]) == 0
assert theStatus.count(statusKeys[3]) == 0
# Reorder
# =======
cOrder = list(theStatus.keys())
assert cOrder == statusKeys
# Wrong length
assert theStatus.reorder([]) is False
# No change
assert theStatus.reorder(cOrder) is False
# Actual reaorder
nOrder = [
statusKeys[0],
statusKeys[2],
statusKeys[1],
statusKeys[3],
]
assert theStatus.reorder(nOrder) is True
assert list(theStatus.keys()) == nOrder
# Add an unknown key
wOrder = nOrder.copy()
wOrder[3] = theStatus._newKey()
assert theStatus.reorder(wOrder) is False
assert list(theStatus.keys()) == nOrder
# Put it back
assert theStatus.reorder(cOrder) is True
assert list(theStatus.keys()) == cOrder
# Default
# =======
default = theStatus._default
theStatus._default = None
assert theStatus.check("Entry 5") == ""
assert theStatus.name("blablabla") == ""
assert theStatus.cols("blablabla") == (100, 100, 100)
assert theStatus.count("blablabla") == 0
assert isinstance(theStatus.icon("blablabla"), QIcon)
theStatus._default = default
# Remove
# ======
# Non-existing entry
assert theStatus.remove("blablabla") is False
# Non-zero entry
theStatus.increment(statusKeys[3])
assert theStatus.remove(statusKeys[3]) is False
# Delete last entry
theStatus.resetCounts()
lastName = theStatus.name(statusKeys[3])
assert lastName == "Entry 4"
assert theStatus.remove(statusKeys[3]) is True
assert theStatus.check(statusKeys[3]) == theStatus._default
assert theStatus.check(lastName) == theStatus._default
# Delete default entry, Entry 2 is new default
firstName = theStatus.name(theStatus._default)
assert firstName == "Entry 1"
assert theStatus.remove(theStatus._default) is True
assert theStatus.name(firstName) == "Entry 2"
# Remove remaining entries
assert theStatus.remove(statusKeys[1]) is True
assert theStatus.remove(statusKeys[2]) is True
assert len(theStatus) == 0
assert theStatus._default is None
# END Test testCoreStatus_Entries
@pytest.mark.core
def testCoreStatus_XMLPackUnpack():
"""Test all the simple setters for the NWItem class.
"""Test all the XML pack/unpack of the NWStatus class.
"""
theStatus = NWStatus()
theStatus.addEntry("New", (100, 100, 100))
theStatus.addEntry("Minor", (200, 50, 0))
theStatus.addEntry("Major", (200, 150, 0))
theStatus.addEntry("Main", (50, 200, 0))
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100))
theStatus.write(None, "Note", (200, 50, 0))
theStatus.write(None, "Draft", (200, 150, 0))
theStatus.write(None, "Finished", (50, 200, 0))
countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo):
for _ in range(n):
theStatus.countEntry(theStatus._theLabels[i])
theStatus.increment(statusKeys[i])
nwXML = etree.Element("novelWriterXML")
@@ -122,24 +335,30 @@ def testCoreStatus_XMLPackUnpack():
xStatus = etree.SubElement(nwXML, "status")
theStatus.packXML(xStatus)
assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == (
b"<status>"
b"<entry blue=\"100\" green=\"100\" red=\"100\">New</entry>"
b"<entry blue=\"0\" green=\"50\" red=\"200\">Minor</entry>"
b"<entry blue=\"0\" green=\"150\" red=\"200\">Major</entry>"
b"<entry blue=\"0\" green=\"200\" red=\"50\">Main</entry>"
b"</status>"
b'<status>'
b'<entry key="sa3b179" count="3" red="100" green="100" blue="100">New</entry>'
b'<entry key="s1c8031" count="5" red="200" green="50" blue="0">Note</entry>'
b'<entry key="s06671a" count="7" red="200" green="150" blue="0">Draft</entry>'
b'<entry key="sbdd640" count="9" red="50" green="200" blue="0">Finished</entry>'
b'</status>'
)
# Unpack
theStatus = NWStatus()
theStatus = NWStatus(NWStatus.STATUS)
assert theStatus.unpackXML(xStatus)
assert theStatus._theLabels == ["New", "Minor", "Major", "Main"]
assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)]
assert theStatus._theCounts == [0, 0, 0, 0]
assert theStatus._theMap["New"] == 0
assert theStatus._theMap["Minor"] == 1
assert theStatus._theMap["Major"] == 2
assert theStatus._theMap["Main"] == 3
assert theStatus._theLength == 4
assert len(theStatus._store) == 4
assert list(theStatus._store.keys()) == statusKeys
assert theStatus._store[statusKeys[0]]["name"] == "New"
assert theStatus._store[statusKeys[1]]["name"] == "Note"
assert theStatus._store[statusKeys[2]]["name"] == "Draft"
assert theStatus._store[statusKeys[3]]["name"] == "Finished"
assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100)
assert theStatus._store[statusKeys[1]]["cols"] == (200, 50, 0)
assert theStatus._store[statusKeys[2]]["cols"] == (200, 150, 0)
assert theStatus._store[statusKeys[3]]["cols"] == (50, 200, 0)
assert theStatus._store[statusKeys[0]]["count"] == countTo[0]
assert theStatus._store[statusKeys[1]]["count"] == countTo[1]
assert theStatus._store[statusKeys[2]]["count"] == countTo[2]
assert theStatus._store[statusKeys[3]]["count"] == countTo[3]
# END Test testCoreStatus_XMLPackUnpack
+1 -4
View File
@@ -24,7 +24,7 @@ import pytest
from tools import readFile
from novelwriter.core import NWProject, NWIndex, ToHtml
from novelwriter.core import NWProject, ToHtml
@pytest.mark.core
@@ -32,7 +32,6 @@ def testCoreToHtml_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToHtml class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject)
# Novel Files Headers
@@ -235,7 +234,6 @@ def testCoreToHtml_ConvertDirect(mockGUI):
"""Test the converter directly using the ToHtml class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject)
theHtml._isNovel = True
@@ -606,7 +604,6 @@ def testCoreToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject)
# Export Mode
+17 -9
View File
@@ -28,12 +28,17 @@ from novelwriter.core import NWProject, NWDoc
from novelwriter.core.tokenizer import Tokenizer
class BareTokenizer(Tokenizer):
def doConvert(self):
super().doConvert()
@pytest.mark.core
def testCoreToken_Setters(mockGUI):
"""Test all the setters for the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
# Verify defaults
assert theToken._fmtTitle == "%title%"
@@ -131,11 +136,10 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
"""Test handling files and text in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
theProject.projLang = "en"
theProject._loadProjectLocalisation()
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True)
assert theProject.openProject(nwMinimal)
@@ -214,6 +218,10 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
"# Notes: Plot\n\n"
)
# Ckeck abstract method
with pytest.raises(NotImplementedError):
theToken.doConvert()
# END Test testCoreToken_TextOps
@@ -222,7 +230,7 @@ def testCoreToken_HeaderFormat(mockGUI):
"""Test the tokenization of header formats in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True)
# Title
@@ -426,7 +434,7 @@ def testCoreToken_MetaFormat(mockGUI):
"""Test the tokenization of meta formats in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True)
# Comment
@@ -495,7 +503,7 @@ def testCoreToken_MarginFormat(mockGUI):
"""Test the tokenization of margin formats in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True)
# Alignment and Indentation
@@ -550,7 +558,7 @@ def testCoreToken_TextFormat(mockGUI):
"""Test the tokenization of text formats in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True)
# Text
@@ -672,7 +680,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"""Test the tokenization of special formats in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
theToken._isNovel = True
@@ -877,7 +885,7 @@ def testCoreToken_ProcessHeaders(mockGUI):
theProject = NWProject(mockGUI)
theProject.projLang = "en"
theProject._loadProjectLocalisation()
theToken = Tokenizer(theProject)
theToken = BareTokenizer(theProject)
# Nothing
theToken._theText = "Some text ...\n"
+1 -4
View File
@@ -24,7 +24,7 @@ import pytest
from tools import readFile
from novelwriter.core import NWProject, NWIndex, ToMarkdown
from novelwriter.core import NWProject, ToMarkdown
@pytest.mark.core
@@ -32,7 +32,6 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theMD = ToMarkdown(theProject)
# Headers
@@ -161,7 +160,6 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
"""Test the converter directly using the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theMD = ToMarkdown(theProject)
theMD._isNovel = True
@@ -266,7 +264,6 @@ def testCoreToMarkdown_Format(mockGUI):
"""Test all the formatters for the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theMD = ToMarkdown(theProject)
assert theMD._formatKeywords("", theMD.A_NONE) == ""
+1 -8
View File
@@ -28,7 +28,7 @@ from shutil import copyfile
from tools import cmpFiles
from novelwriter.core import NWProject, NWIndex, ToOdt
from novelwriter.core import NWProject, ToOdt
from novelwriter.core.toodt import ODTParagraphStyle, ODTTextStyle, XMLParagraph, _mkTag
XML_NS = [
@@ -55,7 +55,6 @@ def testCoreToOdt_Init(mockGUI):
"""Test initialisation of the ODT document.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
# Flat Doc
# ========
@@ -111,7 +110,6 @@ def testCoreToOdt_TextFormatting(mockGUI):
"""Test formatting of paragraphs.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
theDoc.initDocument()
@@ -233,7 +231,6 @@ def testCoreToOdt_Convert(mockGUI):
"""Test the converter of the ToOdt class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
theDoc._isNovel = True
@@ -565,7 +562,6 @@ def testCoreToOdt_ConvertDirect(mockGUI):
otherwise hard to reach conditions.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
theDoc._isNovel = True
@@ -620,7 +616,6 @@ def testCoreToOdt_SaveFlat(mockGUI, fncDir, outDir, refDir):
"""Test the document save functions.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
theDoc._isNovel = True
@@ -657,7 +652,6 @@ def testCoreToOdt_SaveFull(mockGUI, fncDir, outDir, refDir):
"""Test the document save functions.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=False)
theDoc._isNovel = True
@@ -737,7 +731,6 @@ def testCoreToOdt_Format(mockGUI):
"""Test the formatters for the ToOdt class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
assert theDoc._formatSynopsis("synopsis text") == (
+123 -97
View File
@@ -21,9 +21,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import pytest
import random
from lxml import etree
from hashlib import sha256
from tools import readFile
@@ -33,7 +33,7 @@ from novelwriter.constants import nwFiles
@pytest.fixture(scope="function")
def mockItems(mockGUI):
def mockItems(mockGUI, mockRnd):
"""Create a list of mock items.
"""
theProject = NWProject(mockGUI)
@@ -76,7 +76,7 @@ def mockItems(mockGUI):
itemF = NWItem(theProject)
itemF._name = "Trash"
itemF._type = nwItemType.TRASH
itemF._type = nwItemType.ROOT
itemF._class = nwItemClass.TRASH
itemF._expanded = False
@@ -103,7 +103,7 @@ def mockItems(mockGUI):
("a000000000002", None, itemE),
("a000000000003", None, itemF),
("a000000000004", None, itemG),
("b000000000002", "a000000000002", itemH),
("b000000000002", "a000000000004", itemH),
]
return theItems
@@ -116,26 +116,22 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
theProject = NWProject(mockGUI)
theTree = NWTree(theProject)
theTree.setSeed(42)
assert theTree._handleSeed == 42
# Check that tree is empty (calls NWTree.__bool__)
assert not theTree
assert bool(theTree) is False
# Check for archive and trash folders
assert theTree.trashRoot() is None
assert theTree.archiveRoot() is None
assert not theTree.isTrashRoot("a000000000003")
aHandles = []
for tHandle, pHandle, nwItem in mockItems:
aHandles.append(tHandle)
assert theTree.append(tHandle, pHandle, nwItem)
assert theTree.append(tHandle, pHandle, nwItem) is True
assert theTree.updateItemData(tHandle) is True
assert theTree._treeChanged
assert theTree._treeChanged is True
# Check that tree is not empty (calls __bool__)
assert theTree
assert bool(theTree) is True
# Check the number of elements (calls __len__)
assert len(theTree) == len(mockItems)
@@ -149,17 +145,43 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
# Check that we have the correct archive and trash folders
assert theTree.trashRoot() == "a000000000003"
assert theTree.archiveRoot() == "a000000000002"
assert theTree.isTrashRoot("a000000000003")
assert theTree.findRoot(nwItemClass.ARCHIVE) == "a000000000002"
assert theTree.isTrash("a000000000003") is True
assert theTree.isRoot("a000000000002") is True
# Check that we have the root classes
assert theTree.rootClasses() == {
nwItemClass.NOVEL, nwItemClass.CHARACTER, nwItemClass.ARCHIVE, nwItemClass.TRASH
}
# Check the isTrash function
assert theTree.isTrash("0000000000000") is True # Doesn't exist
assert theTree.isTrash("a000000000003") is True # This the trash folder
theTree["a000000000003"].setClass(nwItemClass.NO_CLASS)
assert theTree.isTrash("a000000000003") is True # This is still trash
theTree["a000000000003"].setClass(nwItemClass.TRASH)
assert theTree.isTrash("b000000000002") is False # This is not trash
value = theTree["b000000000002"].itemParent
theTree["b000000000002"].setParent("a000000000003")
assert theTree.isTrash("b000000000002") is True # This is in trash
theTree["b000000000002"].setParent(value)
value = theTree["b000000000002"].itemRoot
theTree["b000000000002"].setRoot("a000000000003")
assert theTree.isTrash("b000000000002") is True # This is in trash
theTree["b000000000002"].setRoot(value)
# Try to add another trash folder
itemT = NWItem(theProject)
itemT._name = "Trash"
itemT._type = nwItemType.TRASH
itemT._type = nwItemType.ROOT
itemT._class = nwItemClass.TRASH
itemT._expanded = False
assert not theTree.append("1234567890abc", None, itemT)
assert theTree.append("1234567890abc", None, itemT) is False
assert len(theTree) == len(mockItems)
# Generate handle automatically
@@ -169,14 +191,16 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
itemT._class = nwItemClass.NOVEL
itemT._layout = nwItemLayout.DOCUMENT
assert theTree.append(None, None, itemT)
assert theTree.append(None, None, itemT) is True
assert theTree.updateItemData(itemT.itemHandle) is True
assert len(theTree) == len(mockItems) + 1
theList = theTree.handles()
assert theList[-1] == "73475cb40a568"
nHandle = "0000000000010"
assert theList[-1] == nHandle
# Try to add existing handle
assert not theTree.append("73475cb40a568", None, itemT)
assert theTree.append(nHandle, None, itemT) is False
assert len(theTree) == len(mockItems) + 1
# Delete a non-existing item
@@ -184,9 +208,9 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
assert len(theTree) == len(mockItems) + 1
# Delete the last item
del theTree["73475cb40a568"]
del theTree[nHandle]
assert len(theTree) == len(mockItems)
assert "73475cb40a568" not in theTree
assert nHandle not in theTree
# Delete the Novel, Archive and Trash folders
del theTree["a000000000001"]
@@ -196,7 +220,6 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
del theTree["a000000000002"]
assert len(theTree) == len(mockItems) - 2
assert "a000000000002" not in theTree
assert theTree.archiveRoot() is None
del theTree["a000000000003"]
assert len(theTree) == len(mockItems) - 3
@@ -215,31 +238,43 @@ def testCoreTree_Methods(mockGUI, mockItems):
for tHandle, pHandle, nwItem in mockItems:
theTree.append(tHandle, pHandle, nwItem)
theTree.updateItemData(tHandle)
assert len(theTree) == len(mockItems)
# Update item data, nonsense handle
assert theTree.updateItemData("stuff") is False
# Update item data, invalid item parent
corrParent = theTree["b000000000001"].itemParent
theTree["b000000000001"].setParent("0000000000000")
assert theTree.updateItemData("b000000000001") is False
# Update item data, valid item parent
theTree["b000000000001"].setParent(corrParent)
assert theTree.updateItemData("b000000000001") is True
# Update item data, root is unreachable
maxDepth = theTree.MAX_DEPTH
theTree.MAX_DEPTH = 0
with pytest.raises(RecursionError):
theTree.updateItemData("b000000000001")
theTree.MAX_DEPTH = maxDepth
# Chech type
assert theTree.checkType("blabla", nwItemType.FILE) is False
assert theTree.checkType("b000000000001", nwItemType.FILE) is False
assert theTree.checkType("c000000000001", nwItemType.FILE) is True
# Root item lookup
theTree._treeRoots.append("stuff")
assert theTree.findRoot(nwItemClass.WORLD) is None
assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001"
assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004"
# Check for root uniqueness
assert theTree.checkRootUnique(nwItemClass.CUSTOM)
assert theTree.checkRootUnique(nwItemClass.WORLD)
assert not theTree.checkRootUnique(nwItemClass.NOVEL)
assert not theTree.checkRootUnique(nwItemClass.CHARACTER)
# Find root item of child item
assert theTree.getRootItem("b000000000001").itemHandle == "a000000000001"
assert theTree.getRootItem("c000000000001").itemHandle == "a000000000001"
assert theTree.getRootItem("c000000000002").itemHandle == "a000000000001"
assert theTree.getRootItem("stuff") is None
# Add a fake item to root and check that it can handle it
theTree._treeRoots["0000000000000"] = NWItem(theProject)
assert theTree.findRoot(nwItemClass.WORLD) is None
del theTree._treeRoots["0000000000000"]
# Get item path
assert theTree.getItemPath("stuff") == []
@@ -247,6 +282,13 @@ def testCoreTree_Methods(mockGUI, mockItems):
"c000000000001", "b000000000001", "a000000000001"
]
# Cause recursion error
maxDepth = theTree.MAX_DEPTH
theTree.MAX_DEPTH = 0
with pytest.raises(RecursionError):
theTree.getItemPath("c000000000001")
theTree.MAX_DEPTH = maxDepth
# Break the folder parent handle
theTree["b000000000001"]._parent = "stuff"
assert theTree.getItemPath("c000000000001") == [
@@ -269,44 +311,31 @@ def testCoreTree_Methods(mockGUI, mockItems):
@pytest.mark.core
def testCoreTree_MakeHandles(monkeypatch, mockGUI):
def testCoreTree_MakeHandles(mockGUI):
"""Test generating item handles.
"""
random.seed(42)
theProject = NWProject(mockGUI)
theTree = NWTree(theProject)
theTree.setSeed(42)
handles = ["1c803a3b1799d", "bdd6406671ad1", "3eb1346685257", "23b8c392456de"]
random.seed(42)
tHandle = theTree._makeHandle()
assert tHandle == "73475cb40a568"
assert tHandle == handles[0]
theTree._projTree[handles[0]] = None
# Add the next in line to the project to force duplicate
theTree._projTree["44cb730c42048"] = None
theTree._projTree[handles[1]] = None
tHandle = theTree._makeHandle()
assert tHandle == "71ee45a3c0db9"
# Fix the time() function and force a handle collission
theTree.setSeed(None)
theTree._handleCount = 0
monkeypatch.setattr("novelwriter.core.tree.time", lambda: 123.4)
assert tHandle == handles[2]
theTree._projTree[handles[2]] = None
# Reset the seed to force collissions, which should still end up
# returning the next handle in the sequence
random.seed(42)
tHandle = theTree._makeHandle()
theTree._projTree[tHandle] = None
newSeed = "123.4_0_"
assert tHandle == sha256(newSeed.encode()).hexdigest()[0:13]
tHandle = theTree._makeHandle()
theTree._projTree[tHandle] = None
newSeed = "123.4_1_"
assert tHandle == sha256(newSeed.encode()).hexdigest()[0:13]
# Reset the count and the handle for 0 and 1 should be duplicates
# which forces the function to add the '!'
theTree._handleCount = 0
tHandle = theTree._makeHandle()
theTree._projTree[tHandle] = None
newSeed = "123.4_1_!"
assert tHandle == sha256(newSeed.encode()).hexdigest()[0:13]
assert tHandle == handles[3]
# END Test testCoreTree_MakeHandles
@@ -329,12 +358,6 @@ def testCoreTree_Stats(mockGUI, mockItems):
assert novelWords == 550
assert noteWords == 400
# Count types
nRoot, nFolder, nFile = theTree.countTypes()
assert nRoot == 3
assert nFolder == 1
assert nFile == 3
# END Test testCoreTree_Stats
@@ -379,42 +402,44 @@ def testCoreTree_XMLPackUnpack(mockGUI, mockItems):
for tHandle, pHandle, nwItem in mockItems:
theTree.append(tHandle, pHandle, nwItem)
theTree.updateItemData(tHandle)
assert len(theTree) == len(mockItems)
nwXML = etree.Element("novelWriterXML")
theTree.packXML(nwXML)
assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == (
b"<novelWriterXML>"
b"<content count=\"8\">"
b"<item handle=\"a000000000001\" order=\"0\" parent=\"None\">"
b"<name>Novel</name><type>ROOT</type><class>NOVEL</class><status>None</status>"
b"<expanded>True</expanded></item>"
b"<item handle=\"b000000000001\" order=\"0\" parent=\"a000000000001\">"
b"<name>Act One</name><type>FOLDER</type><class>NOVEL</class><status>None</status>"
b"<expanded>True</expanded></item>"
b"<item handle=\"c000000000001\" order=\"0\" parent=\"b000000000001\">"
b"<name>Chapter One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
b"<exported>True</exported><layout>DOCUMENT</layout><charCount>300</charCount>"
b"<wordCount>50</wordCount><paraCount>2</paraCount><cursorPos>0</cursorPos></item>"
b"<item handle=\"c000000000002\" order=\"0\" parent=\"b000000000001\">"
b"<name>Scene One</name><type>FILE</type><class>NOVEL</class><status>None</status>"
b"<exported>True</exported><layout>DOCUMENT</layout><charCount>3000</charCount>"
b"<wordCount>500</wordCount><paraCount>20</paraCount><cursorPos>0</cursorPos></item>"
b"<item handle=\"a000000000002\" order=\"0\" parent=\"None\">"
b"<name>Outtakes</name><type>ROOT</type><class>ARCHIVE</class><status>None</status>"
b"<expanded>False</expanded></item>"
b"<item handle=\"a000000000003\" order=\"0\" parent=\"None\">"
b"<name>Trash</name><type>TRASH</type><class>TRASH</class><status>None</status>"
b"<expanded>False</expanded></item>"
b"<item handle=\"a000000000004\" order=\"0\" parent=\"None\">"
b"<name>Characters</name><type>ROOT</type><class>CHARACTER</class><status>None</status>"
b"<expanded>True</expanded></item>"
b"<item handle=\"b000000000002\" order=\"0\" parent=\"a000000000002\">"
b"<name>Jane Doe</name><type>FILE</type><class>CHARACTER</class><status>None</status>"
b"<exported>True</exported><layout>NOTE</layout><charCount>2000</charCount>"
b"<wordCount>400</wordCount><paraCount>16</paraCount><cursorPos>0</cursorPos></item>"
b"</content></novelWriterXML>"
b'<novelWriterXML>'
b'<content count="8">'
b'<item handle="a000000000001" parent="None" root="a000000000001" order="0" type="ROOT" '
b'class="NOVEL"><meta expanded="True"/><name status="s000000" '
b'import="i000004">Novel</name></item>'
b'<item handle="b000000000001" parent="a000000000001" root="a000000000001" order="0" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="s000000" '
b'import="i000004">Act One</name></item>'
b'<item handle="c000000000001" parent="b000000000001" root="a000000000001" order="0" '
b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta expanded="False" charCount="300" '
b'wordCount="50" paraCount="2" cursorPos="0"/><name status="s000000" import="i000004" '
b'exported="True">Chapter One</name></item>'
b'<item handle="c000000000002" parent="b000000000001" root="a000000000001" order="0" '
b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta expanded="False" charCount="3000" '
b'wordCount="500" paraCount="20" cursorPos="0"/><name status="s000000" import="i000004" '
b'exported="True">Scene One</name></item>'
b'<item handle="a000000000002" parent="None" root="a000000000002" order="0" type="ROOT" '
b'class="ARCHIVE"><meta expanded="False"/><name status="s000000" '
b'import="i000004">Outtakes</name></item>'
b'<item handle="a000000000003" parent="None" root="a000000000003" order="0" type="ROOT" '
b'class="TRASH"><meta expanded="False"/><name status="s000000" '
b'import="i000004">Trash</name></item>'
b'<item handle="a000000000004" parent="None" root="a000000000004" order="0" type="ROOT" '
b'class="CHARACTER"><meta expanded="True"/><name status="s000000" '
b'import="i000004">Characters</name></item>'
b'<item handle="b000000000002" parent="a000000000004" root="a000000000004" order="0" '
b'type="FILE" class="CHARACTER" layout="NOTE"><meta expanded="False" charCount="2000" '
b'wordCount="400" paraCount="16" cursorPos="0"/><name status="s000000" import="i000004" '
b'exported="True">Jane Doe</name></item>'
b'</content>'
b'</novelWriterXML>'
)
theTree.clear()
@@ -435,6 +460,7 @@ def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
for tHandle, pHandle, nwItem in mockItems:
theTree.append(tHandle, pHandle, nwItem)
theTree.updateItemData(tHandle)
assert len(theTree) == len(mockItems)
theTree._treeOrder.append("stuff")