Add test coverage of DocDuplicator class
This commit is contained in:
@@ -292,6 +292,7 @@ class DocDuplicator:
|
||||
hMap[tHandle] = newItem.itemHandle
|
||||
if newItem.itemParent in hMap:
|
||||
newItem.setParent(hMap[newItem.itemParent])
|
||||
self._project.tree.updateItemData(newItem.itemHandle)
|
||||
if newItem.isFileType():
|
||||
oldDoc = self._project.storage.getDocument(tHandle)
|
||||
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.1-beta1" hexVersion="0x020100b1" fileVersion="1.5" fileRevision="1" timeStamp="2023-07-20 20:33:41">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>yes</doBackup>
|
||||
<language>None</language>
|
||||
<spellChecking auto="no">None</spellChecking>
|
||||
<lastHandle>
|
||||
<entry key="editor">None</entry>
|
||||
<entry key="viewer">None</entry>
|
||||
<entry key="novelTree">None</entry>
|
||||
<entry key="outline">None</entry>
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="15" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="3" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="18" novelWords="26" notesWords="0">
|
||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">Novel</name>
|
||||
</item>
|
||||
<item handle="0000000000009" parent="None" root="0000000000009" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">Plot</name>
|
||||
</item>
|
||||
<item handle="000000000000a" parent="None" root="000000000000a" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">Characters</name>
|
||||
</item>
|
||||
<item handle="000000000000b" parent="None" root="000000000000b" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">World</name>
|
||||
</item>
|
||||
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">Title Page</name>
|
||||
</item>
|
||||
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">New Chapter</name>
|
||||
</item>
|
||||
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Chapter</name>
|
||||
</item>
|
||||
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Scene</name>
|
||||
</item>
|
||||
<item handle="0000000000010" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Scene</name>
|
||||
</item>
|
||||
<item handle="0000000000011" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0000000000012" parent="0000000000011" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0000000000013" parent="0000000000011" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Scene</name>
|
||||
</item>
|
||||
<item handle="0000000000014" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">Novel</name>
|
||||
</item>
|
||||
<item handle="0000000000015" parent="0000000000014" root="0000000000014" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">Title Page</name>
|
||||
</item>
|
||||
<item handle="0000000000016" parent="0000000000014" root="0000000000014" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="no" />
|
||||
<name status="s000000" import="i000004">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0000000000017" parent="0000000000016" root="0000000000014" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0000000000018" parent="0000000000016" root="0000000000014" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Scene</name>
|
||||
</item>
|
||||
<item handle="0000000000019" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0" />
|
||||
<name status="s000000" import="i000004" active="yes">New Chapter</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
@@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
import pytest
|
||||
|
||||
@@ -31,13 +32,12 @@ from tools import C, buildTestProject, cmpFiles, XML_IGNORE
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.constants import nwItemClass
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder
|
||||
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter, ProjectBuilder
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ipsumText):
|
||||
"""Test the DocMerger utility.
|
||||
"""
|
||||
"""Test the DocMerger utility."""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
@@ -125,8 +125,7 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText):
|
||||
"""Test the DocSplitter utility.
|
||||
"""
|
||||
"""Test the DocSplitter utility."""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
@@ -263,6 +262,143 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText)
|
||||
# END Test testCoreTools_DocSplitter
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd):
|
||||
"""Test the DocDuplicator utility."""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
dup = DocDuplicator(theProject)
|
||||
|
||||
ttText = "#! New Novel\n\n>> By Jane Doe <<\n"
|
||||
chText = "## New Chapter\n\n"
|
||||
scText = "### New Scene\n\n"
|
||||
|
||||
# Check document content
|
||||
assert theProject.storage.getDocument(C.hTitlePage).readDocument() == ttText
|
||||
assert theProject.storage.getDocument(C.hChapterDoc).readDocument() == chText
|
||||
assert theProject.storage.getDocument(C.hSceneDoc).readDocument() == scText
|
||||
|
||||
# Nothing to do
|
||||
assert list(dup.duplicate([])) == []
|
||||
|
||||
# Single Document
|
||||
# ===============
|
||||
|
||||
# A new copy is created
|
||||
assert list(dup.duplicate([C.hSceneDoc])) == [
|
||||
("0000000000010", C.hSceneDoc), # The Scene
|
||||
]
|
||||
assert theProject.tree._treeOrder == [
|
||||
C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot,
|
||||
C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010",
|
||||
]
|
||||
|
||||
# With the same content
|
||||
assert theProject.storage.getDocument("0000000000010").readDocument() == scText
|
||||
|
||||
# They should have the same parent
|
||||
assert theProject.tree["0000000000010"].itemParent == C.hChapterDir # type: ignore
|
||||
|
||||
# Folder w/Two Files
|
||||
# ==================
|
||||
|
||||
# The folder is copied, with two docs
|
||||
assert list(dup.duplicate([C.hChapterDir, C.hChapterDoc, C.hSceneDoc])) == [
|
||||
("0000000000011", C.hChapterDir), # The Folder
|
||||
("0000000000012", None), # The Chapter
|
||||
("0000000000013", None), # The Scene
|
||||
]
|
||||
assert theProject.tree._treeOrder == [
|
||||
C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot,
|
||||
C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010",
|
||||
"0000000000011", "0000000000012", "0000000000013",
|
||||
]
|
||||
|
||||
# With the same content
|
||||
assert theProject.storage.getDocument("0000000000012").readDocument() == chText
|
||||
assert theProject.storage.getDocument("0000000000013").readDocument() == scText
|
||||
|
||||
# The chapter dirs should have the same parent
|
||||
assert theProject.tree["0000000000011"].itemParent == C.hNovelRoot # type: ignore
|
||||
|
||||
# The new files should have the new folder as parent
|
||||
assert theProject.tree["0000000000012"].itemParent == "0000000000011" # type: ignore
|
||||
assert theProject.tree["0000000000013"].itemParent == "0000000000011" # type: ignore
|
||||
|
||||
# Full Root Folder
|
||||
# ================
|
||||
|
||||
# The root is copied, with three docs and a folder
|
||||
assert list(dup.duplicate(
|
||||
[C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc]
|
||||
)) == [
|
||||
("0000000000014", C.hNovelRoot), # The Root
|
||||
("0000000000015", None), # The Title Page
|
||||
("0000000000016", None), # The Folder
|
||||
("0000000000017", None), # The Chapter
|
||||
("0000000000018", None), # The Scene
|
||||
]
|
||||
assert theProject.tree._treeOrder == [
|
||||
C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot,
|
||||
C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010",
|
||||
"0000000000011", "0000000000012", "0000000000013",
|
||||
"0000000000014", "0000000000015", "0000000000016", "0000000000017", "0000000000018",
|
||||
]
|
||||
|
||||
# With the same content
|
||||
assert theProject.storage.getDocument("0000000000015").readDocument() == ttText
|
||||
assert theProject.storage.getDocument("0000000000017").readDocument() == chText
|
||||
assert theProject.storage.getDocument("0000000000018").readDocument() == scText
|
||||
|
||||
# The root folder should have no parent
|
||||
assert theProject.tree["0000000000014"].itemParent is None # type: ignore
|
||||
|
||||
# The folder and files should have the new root
|
||||
assert theProject.tree["0000000000015"].itemRoot == "0000000000014" # type: ignore
|
||||
assert theProject.tree["0000000000016"].itemRoot == "0000000000014" # type: ignore
|
||||
assert theProject.tree["0000000000017"].itemRoot == "0000000000014" # type: ignore
|
||||
assert theProject.tree["0000000000018"].itemRoot == "0000000000014" # type: ignore
|
||||
|
||||
# And they should have new parents
|
||||
assert theProject.tree["0000000000015"].itemParent == "0000000000014" # type: ignore
|
||||
assert theProject.tree["0000000000016"].itemParent == "0000000000014" # type: ignore
|
||||
assert theProject.tree["0000000000017"].itemParent == "0000000000016" # type: ignore
|
||||
assert theProject.tree["0000000000018"].itemParent == "0000000000016" # type: ignore
|
||||
|
||||
# Exceptions
|
||||
# ==========
|
||||
|
||||
# Handle invalid items
|
||||
assert list(dup.duplicate([C.hInvalid])) == []
|
||||
|
||||
# Also stop early if invalid items are encountered
|
||||
assert list(dup.duplicate([C.hInvalid, C.hSceneDoc])) == []
|
||||
|
||||
# Don't overwrite existing files
|
||||
content = theProject.storage.contentPath
|
||||
assert isinstance(content, Path)
|
||||
(content / "0000000000019.nwd").touch()
|
||||
assert (content / "0000000000019.nwd").exists()
|
||||
assert list(dup.duplicate([C.hChapterDoc, C.hSceneDoc])) == []
|
||||
|
||||
# Save and Close
|
||||
theProject.saveProject()
|
||||
|
||||
projFile = fncPath / "nwProject.nwx"
|
||||
testFile = tstPaths.outDir / "coreTools_DocDuplicator_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreTools_DocDuplicator_nwProject.nwx"
|
||||
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
|
||||
|
||||
# END Test testCoreTools_DocDuplicator
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
"""Create a new project from a project wizard dictionary. With
|
||||
|
||||
Reference in New Issue
Block a user