", "this").replace("", "that")
+
+ nDoc = NWDoc(theProject, dummyGUI)
+ nDoc.openDocument(sHandle)
+ nDoc.saveDocument(docText)
+ nDoc.clearDocument()
+
+ theProject.setAutoReplace({"A": "this", "B": "that"})
+
+ assert theProject.saveProject()
+
+ # Root heading
+ assert theToken.addRootHeading("dummy") is False
+ assert theToken.addRootHeading(sHandle) is False
+ assert theToken.addRootHeading("7695ce551d265") is True
+ assert theToken.theMarkdown == "# Notes: Plot\n\n"
+
+ # Set text
+ assert theToken.setText("dummy") is False
+ assert theToken.setText(sHandle) is True
+ assert theToken.theText == docText
+
+ monkeypatch.setattr("nw.constants.nwConst.MAX_DOCSIZE", 100)
+ assert theToken.setText(sHandle, docText) is True
+ assert theToken.theText == (
+ "# ERROR\n\n"
+ "Document 'New Scene' is too big (0.00 MB). Skipping.\n\n"
+ )
+ monkeypatch.undo()
+
+ assert theToken.setText(sHandle, docText) is True
+ assert theToken.theText == docText
+
+ assert theToken.isNone is False
+ assert theToken.isTitle is False
+ assert theToken.isBook is False
+ assert theToken.isPage is False
+ assert theToken.isPart is False
+ assert theToken.isUnNum is False
+ assert theToken.isChap is False
+ assert theToken.isScene is True
+ assert theToken.isNote is False
+ assert theToken.isNovel is True
+
+ # Auto replace
+ theToken.doAutoReplace()
+ assert theToken.theText == docTextR
+
+ # Access
+ assert theToken.getResult() is None
+ assert theToken.getResultSize() == 0
+ theToken.theResult = ""
+ assert theToken.getResultSize() == 0
+
+ # Post Processing
+ theToken.theResult = r"This is text with escapes: \** \~~ \__"
+ theToken.doPostProcessing()
+ assert theToken.theResult == "This is text with escapes: ** ~~ __"
+
+# END Test testCoreToken_TextOps
+
+@pytest.mark.core
+def testCoreToken_Tokenize(dummyGUI):
+ """Test the tokenization of the Tokenizer class.
+ """
+ theProject = NWProject(dummyGUI)
+ theToken = Tokenizer(theProject, dummyGUI)
+
+ # Header 1
+ theToken.theText = "# Novel Title\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD1, 1, "Novel Title", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "# Novel Title\n\n"
+
+ # Header 2
+ theToken.theText = "## Chapter One\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "## Chapter One\n\n"
+
+ # Header 3
+ theToken.theText = "### Scene One\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD3, 1, "Scene One", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "### Scene One\n\n"
+
+ # Header 4
+ theToken.theText = "#### A Section\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD4, 1, "A Section", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "#### A Section\n\n"
+
+ # Comment
+ theToken.theText = "% A comment\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_COMMENT, 1, "A comment", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "\n"
+
+ theToken.setComments(True)
+ theToken.tokenizeText()
+ assert theToken.theMarkdown == "% A comment\n\n"
+
+ # Symopsis
+ theToken.theText = "%synopsis: The synopsis\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ theToken.theText = "% synopsis: The synopsis\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "\n"
+
+ theToken.setSynopsis(True)
+ theToken.tokenizeText()
+ assert theToken.theMarkdown == "% synopsis: The synopsis\n\n"
+
+ # Keyword
+ theToken.theText = "@char: Bod\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_KEYWORD, 1, "char: Bod", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "\n"
+
+ theToken.setKeywords(True)
+ theToken.tokenizeText()
+ assert theToken.theMarkdown == "@char: Bod\n\n"
+
+ # Text
+ theToken.theText = "Some plain text\non two lines\n\n\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_TEXT, 1, "Some plain text", [], Tokenizer.A_NONE),
+ (Tokenizer.T_TEXT, 2, "on two lines", [], Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "Some plain text\non two lines\n\n\n\n"
+
+ theToken.setBodyText(False)
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "\n\n\n"
+ theToken.setBodyText(True)
+
+ # Text Emphasis
+ theToken.theText = "Some **bolded text** on this lines\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (
+ Tokenizer.T_TEXT, 1,
+ "Some **bolded text** on this lines",
+ [
+ [5, 2, Tokenizer.FMT_B_B],
+ [18, 2, Tokenizer.FMT_B_E],
+ ],
+ Tokenizer.A_NONE
+ ),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "Some **bolded text** on this lines\n\n"
+
+ theToken.theText = "Some _italic text_ on this lines\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (
+ Tokenizer.T_TEXT, 1,
+ "Some _italic text_ on this lines",
+ [
+ [5, 1, Tokenizer.FMT_I_B],
+ [17, 1, Tokenizer.FMT_I_E],
+ ],
+ Tokenizer.A_NONE
+ ),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "Some _italic text_ on this lines\n\n"
+
+ theToken.theText = "Some **_bold italic text_** on this lines\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (
+ Tokenizer.T_TEXT, 1,
+ "Some **_bold italic text_** on this lines",
+ [
+ [5, 2, Tokenizer.FMT_B_B],
+ [7, 1, Tokenizer.FMT_I_B],
+ [24, 1, Tokenizer.FMT_I_E],
+ [25, 2, Tokenizer.FMT_B_E],
+ ],
+ Tokenizer.A_NONE
+ ),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "Some **_bold italic text_** on this lines\n\n"
+
+ theToken.theText = "Some ~~strikethrough text~~ on this lines\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (
+ Tokenizer.T_TEXT, 1,
+ "Some ~~strikethrough text~~ on this lines",
+ [
+ [5, 2, Tokenizer.FMT_D_B],
+ [25, 2, Tokenizer.FMT_D_E],
+ ],
+ Tokenizer.A_NONE
+ ),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == "Some ~~strikethrough text~~ on this lines\n\n"
+
+ theToken.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
+ theToken.tokenizeText()
+ assert theToken.theTokens == [
+ (
+ Tokenizer.T_TEXT, 1,
+ "Some **nested bold and _italic_ and ~~strikethrough~~ text** here",
+ [
+ [5, 2, Tokenizer.FMT_B_B],
+ [23, 1, Tokenizer.FMT_I_B],
+ [30, 1, Tokenizer.FMT_I_E],
+ [36, 2, Tokenizer.FMT_D_B],
+ [51, 2, Tokenizer.FMT_D_E],
+ [58, 2, Tokenizer.FMT_B_E],
+ ],
+ Tokenizer.A_NONE
+ ),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+ assert theToken.theMarkdown == (
+ "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
+ )
+
+ # Check the markdown function as well
+ assert theToken.getFilteredMarkdown() == (
+ "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
+ )
+
+# END Test testCoreToken_Tokenize
+
+@pytest.mark.core
+def testCoreToken_Headers(dummyGUI):
+ """Test the header and page parser of the Tokenizer class.
+ """
+ theProject = NWProject(dummyGUI)
+ theToken = Tokenizer(theProject, dummyGUI)
+
+ # Nothing
+ theToken.theText = "Some text ...\n"
+ assert theToken.doHeaders() is True
+ theToken.isNone = True
+ assert theToken.doHeaders() is False
+ theToken.isNone = False
+ assert theToken.doHeaders() is True
+ theToken.isNote = True
+ assert theToken.doHeaders() is False
+ theToken.isNote = False
+
+ ##
+ # Novel
+ ##
+
+ theToken.isNovel = True
+
+ # Titles
+ # ======
+
+ # H1: Title
+ theToken.theText = "# Novel Title\n"
+ theToken.setTitleFormat(r"T: %title%")
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD1, 1, "T: Novel Title", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # Chapters
+ # ========
+
+ # H2: Chapter
+ theToken.theText = "## Chapter One\n"
+ theToken.setChapterFormat(r"C: %title%")
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "C: Chapter One", None, Tokenizer.A_PBB),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H2: Unnumbered Chapter
+ theToken.theText = "## Chapter One\n"
+ theToken.setUnNumberedFormat(r"U: %title%")
+ theToken.isUnNum = True
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "U: Chapter One", None, Tokenizer.A_PBB),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H2: Unnumbered Chapter with Star
+ theToken.theText = "## *Prologue\n"
+ theToken.setUnNumberedFormat(r"U: %title%")
+ theToken.isUnNum = False
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "U: Prologue", None, Tokenizer.A_PBB),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H2: Chapter Word Number
+ theToken.theText = "## Chapter\n"
+ theToken.setChapterFormat(r"Chapter %chw%")
+ theToken.numChapter = 0
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_PBB),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H2: Chapter Roman Number Upper Case
+ theToken.theText = "## Chapter\n"
+ theToken.setChapterFormat(r"Chapter %chI%")
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "Chapter II", None, Tokenizer.A_PBB),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H2: Chapter Roman Number Lower Case
+ theToken.theText = "## Chapter\n"
+ theToken.setChapterFormat(r"Chapter %chi%")
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD2, 1, "Chapter iii", None, Tokenizer.A_PBB),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # Scenes
+ # ======
+
+ # H3: Scene w/Title
+ theToken.theText = "### Scene One\n"
+ theToken.setSceneFormat(r"S: %title%", False)
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD3, 1, "S: Scene One", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene Hidden wo/Format
+ theToken.theText = "### Scene One\n"
+ theToken.setSceneFormat(r"", True)
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene wo/Format, first
+ theToken.theText = "### Scene One\n"
+ theToken.setSceneFormat(r"", False)
+ theToken.firstScene = True
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene wo/Format, not first
+ theToken.theText = "### Scene One\n"
+ theToken.setSceneFormat(r"", False)
+ theToken.firstScene = False
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene Separator, first
+ theToken.theText = "### Scene One\n"
+ theToken.setSceneFormat(r"* * *", False)
+ theToken.firstScene = True
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene Separator, not first
+ theToken.theText = "### Scene One\n"
+ theToken.setSceneFormat(r"* * *", False)
+ theToken.firstScene = False
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene w/Absolute Number
+ theToken.theText = "### A Scene\n"
+ theToken.setSceneFormat(r"Scene %sca%", False)
+ theToken.numAbsScene = 0
+ theToken.numChScene = 0
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD3, 1, "Scene 1", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H3: Scene w/Chapter Number
+ theToken.theText = "### A Scene\n"
+ theToken.setSceneFormat(r"Scene %ch%.%sc%", False)
+ theToken.numAbsScene = 0
+ theToken.numChScene = 1
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD3, 1, "Scene 3.2", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # Sections
+ # ========
+
+ # H4: Section Hidden wo/Format
+ theToken.theText = "#### A Section\n"
+ theToken.setSectionFormat(r"", True)
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H4: Section Visible wo/Format
+ theToken.theText = "#### A Section\n"
+ theToken.setSectionFormat(r"", False)
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H4: Section w/Format
+ theToken.theText = "#### A Section\n"
+ theToken.setSectionFormat(r"X: %title%", False)
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD4, 1, "X: A Section", None, Tokenizer.A_NONE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # H4: Section Separator
+ theToken.theText = "#### A Section\n"
+ theToken.setSectionFormat(r"* * *", False)
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
+ ]
+
+ # Check the first scene detector
+ assert theToken.firstScene is False
+ theToken.firstScene = True
+ assert theToken.firstScene is True
+ theToken.theText = "Some text ...\n"
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.firstScene is False
+
+ ##
+ # Title or Partition
+ ##
+
+ theToken.isNovel = False
+
+ # H1: Title
+ theToken.theText = "# Novel Title\n"
+ theToken.setTitleFormat(r"T: %title%")
+ theToken.tokenizeText()
+ theToken.isTitle = True
+ theToken.isPart = False
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_TITLE, 1, "Novel Title", None, Tokenizer.A_PBB_NO | Tokenizer.A_CENTRE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_PBA | Tokenizer.A_CENTRE),
+ ]
+
+ # H1: Partition
+ theToken.theText = "# Partition Title\n"
+ theToken.setTitleFormat(r"T: %title%")
+ theToken.tokenizeText()
+ theToken.isTitle = False
+ theToken.isPart = True
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_HEAD1, 1, "Partition Title", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
+ (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_PBA | Tokenizer.A_CENTRE),
+ ]
+
+ ##
+ # Page
+ ##
+
+ theToken.isNovel = False
+ theToken.isTitle = False
+ theToken.isPart = False
+ theToken.isPage = True
+
+ # Some Page Text
+ theToken.theText = "Page text\n\nMore text\n"
+ theToken.tokenizeText()
+ theToken.doHeaders()
+ assert theToken.theTokens == [
+ (Tokenizer.T_TEXT, 1, "Page text", [], Tokenizer.A_PBB | Tokenizer.A_LEFT),
+ (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_LEFT),
+ (Tokenizer.T_TEXT, 3, "More text", [], Tokenizer.A_LEFT),
+ (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_LEFT),
+ ]
+
+# END Test testCoreToken_Headers
From 91ece2b7f284e0644abdd9d5755b340e2c07e0d6 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 4 Dec 2020 20:58:44 +0100
Subject: [PATCH 16/22] New test for ToHtml class
---
nw/core/tohtml.py | 20 +-
nw/gui/docviewer.py | 2 +-
nw/guimain.py | 8 +-
tests/dummy.py | 2 +
tests/test_core_tohtml.py | 373 +++++++++++++++++++++++++++++++++++
tests/test_core_tokenizer.py | 3 +-
6 files changed, 392 insertions(+), 16 deletions(-)
create mode 100644 tests/test_core_tohtml.py
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index df6c4a09..f3166d02 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -68,16 +68,16 @@ class ToHtml(Tokenizer):
# Setters
##
- def setPreview(self, forPreview, doComments, doSynopsis):
+ def setPreview(self, doComments, doSynopsis):
"""If we're using this class to generate markdown preview, we
need to make a few changes to formatting, which is managed by
these flags.
"""
- if forPreview:
- self.genMode = self.M_PREVIEW
- self.doKeywords = True
- self.doComments = doComments
- self.doSynopsis = doSynopsis
+ self.genMode = self.M_PREVIEW
+ self.doKeywords = True
+ self.doComments = doComments
+ self.doSynopsis = doSynopsis
+
return
def setStyles(self, cssStyles):
@@ -141,11 +141,13 @@ class ToHtml(Tokenizer):
# For novel files for export, we bump the titles one level
# up as this is more useful for printing and word processor
# imports.
- h1 = "h1 class='title'"
+ h1Cl = " class='title'"
+ h1 = "h1"
h2 = "h1"
h3 = "h2"
h4 = "h3"
else:
+ h1Cl = ""
h1 = "h1"
h2 = "h2"
h3 = "h3"
@@ -193,7 +195,7 @@ class ToHtml(Tokenizer):
else:
aNm = ""
- # Process TextType
+ # Process Text Type
if tType == self.T_EMPTY:
if parStyle is None:
parStyle = ""
@@ -214,7 +216,7 @@ class ToHtml(Tokenizer):
elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "
")
- tmpResult.append("<%s%s>%s%s%s>\n" % (h1, hStyle, aNm, tHead, h1))
+ tmpResult.append("<%s%s%s>%s%s%s>\n" % (h1, h1Cl, hStyle, aNm, tHead, h1))
elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "
")
diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py
index e74e7ada..f3badad5 100644
--- a/nw/gui/docviewer.py
+++ b/nw/gui/docviewer.py
@@ -169,7 +169,7 @@ class GuiDocViewer(QTextBrowser):
sPos = self.verticalScrollBar().value()
aDoc = ToHtml(self.theProject, self.theParent)
- aDoc.setPreview(True, self.mainConf.viewComments, self.mainConf.viewSynopsis)
+ aDoc.setPreview(self.mainConf.viewComments, self.mainConf.viewSynopsis)
aDoc.setLinkHeaders(True)
# Be extra careful here to prevent crashes when first opening a
diff --git a/nw/guimain.py b/nw/guimain.py
index 801de5af..76b344fb 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -81,14 +81,14 @@ class GuiMain(QMainWindow):
# Core Classes
# ============
- # Core Classes and settings
+ # Core Classes and Settings
self.theTheme = GuiTheme(self)
self.theProject = NWProject(self)
self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False
self.isFocusMode = False
- # Prepare main window
+ # Prepare Main Window
self.resize(*self.mainConf.getWinSize())
self._setWindowTitle()
self.setWindowIcon(QIcon(self.mainConf.appIcon))
@@ -1073,10 +1073,10 @@ class GuiMain(QMainWindow):
self.isFocusMode = not self.isFocusMode
if self.isFocusMode:
- logger.debug("Activating Focus mode")
+ logger.debug("Activating Focus Mode")
self.tabWidget.setCurrentWidget(self.splitDocs)
else:
- logger.debug("Deactivating Focus mode")
+ logger.debug("Deactivating Focus Mode")
isVisible = not self.isFocusMode
self.treePane.setVisible(isVisible)
diff --git a/tests/dummy.py b/tests/dummy.py
index fbb91cbd..803e4e12 100644
--- a/tests/dummy.py
+++ b/tests/dummy.py
@@ -11,6 +11,8 @@ class DummyMain():
def __init__(self):
self.mainConf = None
self.hasProject = True
+ self.theIndex = None
+ self.theProject = None
self.statusBar = StatusBar()
return
diff --git a/tests/test_core_tohtml.py b/tests/test_core_tohtml.py
new file mode 100644
index 00000000..7c228edf
--- /dev/null
+++ b/tests/test_core_tohtml.py
@@ -0,0 +1,373 @@
+# -*- coding: utf-8 -*-
+"""novelWriter ToHtml Class Tester
+"""
+
+import pytest
+
+from nw.core import NWProject, NWIndex, ToHtml
+
+@pytest.mark.core
+def testCoreToHtml_Format(dummyGUI):
+ """Test all the formatters for the ToHtml class.
+ """
+ theProject = NWProject(dummyGUI)
+ dummyGUI.theIndex = NWIndex(theProject, dummyGUI)
+ theHtml = ToHtml(theProject, dummyGUI)
+
+ # Export Mode
+ # ===========
+
+ assert theHtml._formatSynopsis("synopsis text") == (
+ "Synopsis: synopsis text
\n"
+ )
+ assert theHtml._formatComments("comment text") == (
+ "\n"
+ )
+
+ assert theHtml._formatKeywords("") == ""
+ assert theHtml._formatKeywords("tag: Jane") == (
+ ""
+ )
+ assert theHtml._formatKeywords("char: Bod, Jane") == (
+ ""
+ "
Characters: "
+ "
Bod, "
+ "
Jane"
+ "
"
+ )
+
+ # Preview Mode
+ # ============
+
+ theHtml.setPreview(True, True)
+
+ assert theHtml._formatSynopsis("synopsis text") == (
+ "\n"
+ )
+ assert theHtml._formatComments("comment text") == (
+ "\n"
+ )
+
+ assert theHtml._formatKeywords("") == ""
+ assert theHtml._formatKeywords("tag: Jane") == (
+ ""
+ )
+ assert theHtml._formatKeywords("char: Bod, Jane") == (
+ ""
+ "
Characters: "
+ "
Bod, "
+ "
Jane"
+ "
"
+ )
+
+# END Test testCoreToHtml_Format
+
+@pytest.mark.core
+def testCoreToHtml_Convert(dummyGUI):
+ """Test the converter of the ToHtml class.
+ """
+ theProject = NWProject(dummyGUI)
+ dummyGUI.theIndex = NWIndex(theProject, dummyGUI)
+ theHtml = ToHtml(theProject, dummyGUI)
+
+ # Export Mode
+ # ===========
+
+ theHtml.isNovel = True
+
+ # Header 1
+ theHtml.theText = "# Title\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Title
\n"
+
+ # Header 2
+ theHtml.theText = "## Chapter Title\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Chapter Title
\n"
+
+ # Header 3
+ theHtml.theText = "### Scene Title\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Scene Title
\n"
+
+ # Header 4
+ theHtml.theText = "#### Section Title\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Section Title
\n"
+
+ theHtml.isNovel = False
+ theHtml.setLinkHeaders(True)
+
+ # Header 1
+ theHtml.theText = "# Heading One\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Heading One
\n"
+
+ # Header 2
+ theHtml.theText = "## Heading Two\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Heading Two
\n"
+
+ # Header 3
+ theHtml.theText = "### Heading Three\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Heading Three
\n"
+
+ # Header 4
+ theHtml.theText = "#### Heading Four\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == "Heading Four
\n"
+
+ # Text
+ theHtml.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "Some nested bold and italic and "
+ "strikethrough text here
\n"
+ )
+
+ # Text w/Hard Break
+ theHtml.theText = "Line one \nLine two \nLine three\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "Line one
Line two
Line three
\n"
+ )
+
+ # Synopsis
+ theHtml.theText = "%synopsis: The synopsis ...\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == ""
+
+ theHtml.setSynopsis(True)
+ theHtml.theText = "%synopsis: The synopsis ...\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "Synopsis: The synopsis ...
\n"
+ )
+
+ # Comment
+ theHtml.theText = "% A comment ...\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == ""
+
+ theHtml.setComments(True)
+ theHtml.theText = "% A comment ...\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "\n"
+ )
+
+ # Keywords
+ theHtml.theText = "@char: Bod, Jane\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == ""
+
+ theHtml.setKeywords(True)
+ theHtml.theText = "@char: Bod, Jane\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ ""
+ )
+
+ # Direct Tests
+ # ============
+
+ theHtml.isNovel = True
+
+ # Title
+ theHtml.theTokens = [
+ (theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB_NO | theHtml.A_CENTRE),
+ (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ ""
+ "A Title
\n"
+ )
+
+ # Separator
+ theHtml.theTokens = [
+ (theHtml.T_SEP, 1, "* * *", None, theHtml.A_CENTRE),
+ (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == "* * *
\n"
+
+ # Skip
+ theHtml.theTokens = [
+ (theHtml.T_SKIP, 1, "", None, theHtml.A_NONE),
+ (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == "
\n"
+
+ # Styles
+ # ======
+
+ theHtml.setLinkHeaders(False)
+
+ # Align Left
+ theHtml.setStyles(False)
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ theHtml.setStyles(True)
+
+ # Align Left
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Align Right
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_RIGHT),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Align Centre
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_CENTRE),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Align Justify
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_JUSTIFY),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Page Break Always
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB | theHtml.A_PBA),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Page Break Avoid
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_AV | theHtml.A_PBA_AV),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Page Break ANever
+ theHtml.theTokens = [
+ (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_NO | theHtml.A_PBA_NO),
+ ]
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "A Title
\n"
+ )
+
+ # Preview Mode
+ # ============
+
+ theHtml.setPreview(True, True)
+
+ # Text (HTML4)
+ theHtml.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "Some nested bold and italic and "
+ "strikethrough "
+ "text here
\n"
+ )
+
+# END Test testCoreToHtml_Convert
+
+@pytest.mark.core
+def testCoreToHtml_Methods(dummyGUI):
+ """Test all the other methods of the ToHtml class.
+ """
+ theProject = NWProject(dummyGUI)
+ theHtml = ToHtml(theProject, dummyGUI)
+
+ # Auto-Replace
+ docText = "Text with & short–dash, long—dash …\n"
+ theHtml.theText = docText
+ theHtml.doAutoReplace()
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "Text with <brackets> & short–dash, long—dash …
\n"
+ )
+
+ # Revert on MD
+ assert theHtml.theMarkdown == (
+ "Text with <brackets> & short–dash, long—dash …\n\n"
+ )
+ theHtml.doPostProcessing()
+ assert theHtml.theMarkdown == docText + "\n"
+
+ # With Preview, No Revert
+ theHtml.setPreview(True, True)
+ theHtml.theText = docText
+ theHtml.doAutoReplace()
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theMarkdown == (
+ "Text with <brackets> & short–dash, long—dash …\n\n"
+ )
+ theHtml.doPostProcessing()
+ assert theHtml.theMarkdown == (
+ "Text with <brackets> & short–dash, long—dash …\n\n"
+ )
+
+ # CSS
+ # ===
+
+ assert len(theHtml.getStyleSheet()) > 1
+ assert "p {text-align: left;}" in theHtml.getStyleSheet()
+ assert "p {text-align: justify;}" not in theHtml.getStyleSheet()
+
+ theHtml.setJustify(True)
+ assert "p {text-align: left;}" not in theHtml.getStyleSheet()
+ assert "p {text-align: justify;}" in theHtml.getStyleSheet()
+
+ theHtml.setStyles(False)
+ assert theHtml.getStyleSheet() == []
+
+# END Test testCoreToHtml_Methods
diff --git a/tests/test_core_tokenizer.py b/tests/test_core_tokenizer.py
index 559f3a1e..3d0774b5 100644
--- a/tests/test_core_tokenizer.py
+++ b/tests/test_core_tokenizer.py
@@ -12,7 +12,7 @@ def testCoreToken_Setters(dummyGUI):
"""Test all the setters for the Tokenizer class.
"""
theProject = NWProject(dummyGUI)
- theToken = Tokenizer(dummyGUI, theProject)
+ theToken = Tokenizer(theProject, dummyGUI)
# Verify defaults
assert theToken.fmtTitle == "%title%"
@@ -600,7 +600,6 @@ def testCoreToken_Headers(dummyGUI):
# H1: Title
theToken.theText = "# Novel Title\n"
- theToken.setTitleFormat(r"T: %title%")
theToken.tokenizeText()
theToken.isTitle = True
theToken.isPart = False
From 57397fa2bcf419c83de8989cfa8a3eb72198059c Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 4 Dec 2020 21:28:35 +0100
Subject: [PATCH 17/22] Updated existing NWProject tests
---
pytest.ini | 1 -
tests/README.md | 1 +
tests/conftest.py | 36 ++---
...roject.nwx => coreProject_1_nwProject.nwx} | 0
...roject.nwx => coreProject_2_nwProject.nwx} | 0
...roject.nwx => coreProject_3_nwProject.nwx} | 0
...roject.nwx => coreProject_4_nwProject.nwx} | 0
...roject.nwx => coreProject_5_nwProject.nwx} | 0
.../{test_project.py => test_core_project.py} | 150 ++++++++++--------
9 files changed, 101 insertions(+), 87 deletions(-)
rename tests/reference/{proj/1_nwProject.nwx => coreProject_1_nwProject.nwx} (100%)
rename tests/reference/{proj/2_nwProject.nwx => coreProject_2_nwProject.nwx} (100%)
rename tests/reference/{proj/3_nwProject.nwx => coreProject_3_nwProject.nwx} (100%)
rename tests/reference/{proj/4_nwProject.nwx => coreProject_4_nwProject.nwx} (100%)
rename tests/reference/{proj/5_nwProject.nwx => coreProject_5_nwProject.nwx} (100%)
rename tests/{test_project.py => test_core_project.py} (82%)
diff --git a/pytest.ini b/pytest.ini
index 0c3af3ba..b61d59ec 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,6 +1,5 @@
[pytest]
markers =
- project: Project classes tests
error: Test various error handling scenarios
core: Core functionality tests
gui: Qt5 GUI tests
diff --git a/tests/README.md b/tests/README.md
index b9c221f9..82a34712 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -69,4 +69,5 @@ The commands for the respective test categories are listed below.
| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` |
+| Unit | ToHtml class | nw/core/tohtml.py | `-m core` | `-k testCoreToHtml` |
| Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` |
diff --git a/tests/conftest.py b/tests/conftest.py
index 3a97c58f..e6b90511 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -51,6 +51,20 @@ def outDir(tmpDir):
os.mkdir(theDir)
return theDir
+@pytest.fixture(scope="function")
+def fncDir(tmpDir):
+ """A temporary folder for a single test function.
+ """
+ funcDir = os.path.join(tmpDir, "ftemp")
+ if os.path.isdir(funcDir):
+ shutil.rmtree(funcDir)
+ if not os.path.isdir(funcDir):
+ os.mkdir(funcDir)
+ yield funcDir
+ if os.path.isdir(funcDir):
+ shutil.rmtree(funcDir)
+ return
+
##
# novelWriter Objects
##
@@ -158,32 +172,10 @@ def yesToAll(monkeypatch):
# =============================================================================================== #
-##
-# novelWriter Objects
-##
-
-@pytest.fixture(scope="session")
-def nwConf(refDir, tmpDir):
- """Temporary novelWriter configuration used for the dummy instance
- of novelWriter's main GUI.
- """
- theConf = Config()
- theConf.initConfig(refDir, tmpDir)
- return theConf
-
##
# Temporary Test Folders
##
-@pytest.fixture(scope="session")
-def nwTempProj(tmpDir):
- """A temporary folder for project tests.
- """
- projDir = os.path.join(tmpDir, "proj")
- if not os.path.isdir(projDir):
- os.mkdir(projDir)
- return projDir
-
@pytest.fixture(scope="session")
def nwTempGUI(tmpDir):
"""A temporary folder for GUI tests.
diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/coreProject_1_nwProject.nwx
similarity index 100%
rename from tests/reference/proj/1_nwProject.nwx
rename to tests/reference/coreProject_1_nwProject.nwx
diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/coreProject_2_nwProject.nwx
similarity index 100%
rename from tests/reference/proj/2_nwProject.nwx
rename to tests/reference/coreProject_2_nwProject.nwx
diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/coreProject_3_nwProject.nwx
similarity index 100%
rename from tests/reference/proj/3_nwProject.nwx
rename to tests/reference/coreProject_3_nwProject.nwx
diff --git a/tests/reference/proj/4_nwProject.nwx b/tests/reference/coreProject_4_nwProject.nwx
similarity index 100%
rename from tests/reference/proj/4_nwProject.nwx
rename to tests/reference/coreProject_4_nwProject.nwx
diff --git a/tests/reference/proj/5_nwProject.nwx b/tests/reference/coreProject_5_nwProject.nwx
similarity index 100%
rename from tests/reference/proj/5_nwProject.nwx
rename to tests/reference/coreProject_5_nwProject.nwx
diff --git a/tests/test_project.py b/tests/test_core_project.py
similarity index 82%
rename from tests/test_project.py
rename to tests/test_core_project.py
index 748021a7..6cf20033 100644
--- a/tests/test_project.py
+++ b/tests/test_core_project.py
@@ -13,13 +13,13 @@ from tools import cmpFiles
from nw.core.project import NWProject
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
-@pytest.mark.project
-def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI):
- """Test that a basic project can be created, and opened and saved.
+@pytest.mark.core
+def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
+ """Test that a basic project can be created, opened and saved.
"""
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
- testFile = os.path.join(nwTempProj, "1_nwProject.nwx")
- refFile = os.path.join(refDir, "proj", "1_nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_1_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
@@ -28,17 +28,17 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI):
assert not theProject.newProject({})
# Try again with a proper path
- assert theProject.newProject({"projPath": nwFuncTemp})
- assert theProject.setProjectPath(nwFuncTemp)
+ assert theProject.newProject({"projPath": fncDir})
+ assert theProject.setProjectPath(fncDir)
assert theProject.saveProject()
assert theProject.closeProject()
# Creating the project once more should fail
- assert not theProject.newProject({"projPath": nwFuncTemp})
+ assert not theProject.newProject({"projPath": fncDir})
# Check the new project
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
# Open again
assert theProject.openProject(projFile)
@@ -47,7 +47,7 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI):
assert theProject.saveProject()
assert theProject.closeProject()
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
# Open a second time
@@ -57,21 +57,23 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, refDir, tmpDir, dummyGUI):
assert theProject.saveProject()
assert theProject.closeProject()
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
-@pytest.mark.project
-def testProjectNewRoot(nwFuncTemp, nwTempProj, refDir, dummyGUI):
+# END Test testCoreProject_NewOpenSave
+
+@pytest.mark.core
+def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
"""Check that new root folders can be added to the project.
"""
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
- testFile = os.path.join(nwTempProj, "2_nwProject.nwx")
- refFile = os.path.join(refDir, "proj", "2_nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
- assert theProject.newProject({"projPath": nwFuncTemp})
- assert theProject.setProjectPath(nwFuncTemp)
+ assert theProject.newProject({"projPath": fncDir})
+ assert theProject.setProjectPath(fncDir)
assert theProject.saveProject()
assert theProject.closeProject()
assert theProject.openProject(projFile)
@@ -90,22 +92,24 @@ def testProjectNewRoot(nwFuncTemp, nwTempProj, refDir, dummyGUI):
assert theProject.closeProject()
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
-@pytest.mark.project
-def testProjectNewFile(nwFuncTemp, nwTempProj, refDir, dummyGUI):
+# END Test testCoreProject_NewRoot
+
+@pytest.mark.core
+def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
"""Check that new files can be added to the project.
"""
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
- testFile = os.path.join(nwTempProj, "3_nwProject.nwx")
- refFile = os.path.join(refDir, "proj", "3_nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
- assert theProject.newProject({"projPath": nwFuncTemp})
- assert theProject.setProjectPath(nwFuncTemp)
+ assert theProject.newProject({"projPath": fncDir})
+ assert theProject.setProjectPath(fncDir)
assert theProject.saveProject()
assert theProject.closeProject()
assert theProject.openProject(projFile)
@@ -117,23 +121,25 @@ def testProjectNewFile(nwFuncTemp, nwTempProj, refDir, dummyGUI):
assert theProject.closeProject()
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert not theProject.projChanged
-@pytest.mark.project
-def testProjectNewCustomA(nwFuncTemp, nwTempProj, refDir, dummyGUI):
+# END Test testCoreProject_NewFile
+
+@pytest.mark.core
+def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
"""Create a new project from a project wizard dictionary.
Custom type with chapters and scenes.
"""
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
- testFile = os.path.join(nwTempProj, "4_nwProject.nwx")
- refFile = os.path.join(refDir, "proj", "4_nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx")
projData = {
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
- "projPath": nwFuncTemp,
+ "projPath": fncDir,
"popSample": False,
"popMinimal": False,
"popCustom": True,
@@ -157,22 +163,24 @@ def testProjectNewCustomA(nwFuncTemp, nwTempProj, refDir, dummyGUI):
assert theProject.closeProject()
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
-@pytest.mark.project
-def testProjectNewCustomB(nwFuncTemp, nwTempProj, refDir, dummyGUI):
+# END Test testCoreProject_NewCustomA
+
+@pytest.mark.core
+def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
"""Create a new project from a project wizard dictionary.
Custom type without chapters, but with scenes.
"""
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
- testFile = os.path.join(nwTempProj, "5_nwProject.nwx")
- refFile = os.path.join(refDir, "proj", "5_nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx")
projData = {
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
- "projPath": nwFuncTemp,
+ "projPath": fncDir,
"popSample": False,
"popMinimal": False,
"popCustom": True,
@@ -196,10 +204,12 @@ def testProjectNewCustomB(nwFuncTemp, nwTempProj, refDir, dummyGUI):
assert theProject.closeProject()
copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
-@pytest.mark.project
-def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir):
+# END Test testCoreProject_NewCustomB
+
+@pytest.mark.core
+def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir):
"""Check that we can create a new project can be created from the
provided sample project via a zip file.
"""
@@ -207,22 +217,22 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir):
"projName": "Test Sample",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
- "projPath": nwFuncTemp,
+ "projPath": fncDir,
"popSample": True,
"popMinimal": False,
"popCustom": False,
}
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
- theProject.mainConf = nwConf
+ theProject.mainConf = tmpConf
# Sample set, but no path
assert not theProject.newProject({"popSample": True})
# Force the lookup path for assets to our temp folder
- srcSample = os.path.abspath(os.path.join(nwConf.appRoot, "sample"))
+ srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample"))
dstSample = os.path.join(tmpDir, "sample.zip")
- nwConf.assetPath = tmpDir
+ tmpConf.assetPath = tmpDir
# Create and open a defective zip file
with open(dstSample, mode="w+") as outFile:
@@ -239,14 +249,16 @@ def testProjectNewSampleA(nwFuncTemp, nwConf, dummyGUI, tmpDir):
zipObj.write(srcDoc, "content/"+docFile)
assert theProject.newProject(projData)
- assert theProject.openProject(nwFuncTemp)
+ assert theProject.openProject(fncDir)
assert theProject.projName == "Sample Project"
assert theProject.saveProject()
assert theProject.closeProject()
os.unlink(dstSample)
-@pytest.mark.project
-def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir):
+# END Test testCoreProject_NewSampleA
+
+@pytest.mark.core
+def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
"""Check that we can create a new project can be created from the
provided sample project folder.
"""
@@ -254,17 +266,17 @@ def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir):
"projName": "Test Sample",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
- "projPath": nwFuncTemp,
+ "projPath": fncDir,
"popSample": True,
"popMinimal": False,
"popCustom": False,
}
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
- theProject.mainConf = nwConf
+ theProject.mainConf = tmpConf
# Make sure we do not pick up the nw/assets/sample.zip file
- nwConf.assetPath = tmpDir
+ tmpConf.assetPath = tmpDir
# Set a fake project file name
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx")
@@ -272,17 +284,19 @@ def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, dummyGUI, tmpDir):
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx")
assert theProject.newProject(projData)
- assert theProject.openProject(nwFuncTemp)
+ assert theProject.openProject(fncDir)
assert theProject.projName == "Sample Project"
assert theProject.saveProject()
assert theProject.closeProject()
# Misdirect the appRoot path so neither is possible
- nwConf.appRoot = tmpDir
+ tmpConf.appRoot = tmpDir
assert not theProject.newProject(projData)
-@pytest.mark.project
-def testProjectMethods(monkeypatch, nwMinimal, dummyGUI):
+# END Test testCoreProject_NewSampleB
+
+@pytest.mark.core
+def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
"""Test other project class methods and functions.
"""
theProject = NWProject(dummyGUI)
@@ -323,8 +337,10 @@ def testProjectMethods(monkeypatch, nwMinimal, dummyGUI):
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
-@pytest.mark.project
-def testProjectOrphanedFiles(dummyGUI, nwLipsum):
+# END Test testCoreProject_Methods
+
+@pytest.mark.core
+def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
"""Check that files in the content folder that are not tracked in
the project XML file are handled correctly by the orphaned files
function. It should also restore as much meta data as possible from
@@ -392,8 +408,10 @@ def testProjectOrphanedFiles(dummyGUI, nwLipsum):
assert theProject.saveProject(nwLipsum)
assert theProject.closeProject()
-@pytest.mark.project
-def testProjectOldFormat(dummyGUI, nwOldProj):
+# END Test testCoreProject_OrphanedFiles
+
+@pytest.mark.core
+def testCoreProject_OldFormat(dummyGUI, nwOldProj):
"""Test that a project folder structure of version 1.0 can be
converted to the latest folder structure. Version 1.0 split the
documents into 'data_0' ... 'data_f' folders, which are now all
@@ -482,8 +500,10 @@ def testProjectOldFormat(dummyGUI, nwOldProj):
assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionStats.log"))
assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt"))
-@pytest.mark.project
-def testProjectBackup(dummyGUI, nwMinimal, tmpDir):
+# END Test testCoreProject_OldFormat
+
+@pytest.mark.core
+def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
"""Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to
@@ -530,3 +550,5 @@ def testProjectBackup(dummyGUI, nwMinimal, tmpDir):
assert cmpFiles(
os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx")
)
+
+# END Test testCoreProject_Backup
From c17d4de0a5984be468006d7e2dec2bfae5789af0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 5 Dec 2020 19:13:36 +0100
Subject: [PATCH 18/22] Remove QMessageBox dependency in NWProject class
---
nw/core/project.py | 86 +++++++++++++++++++++-------------------------
nw/core/status.py | 3 +-
nw/guimain.py | 7 ++++
3 files changed, 49 insertions(+), 47 deletions(-)
diff --git a/nw/core/project.py b/nw/core/project.py
index f8e45194..b646934a 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -28,12 +28,10 @@
import nw
import logging
import os
+import shutil
from lxml import etree
from time import time
-from shutil import make_archive, unpack_archive, copyfile
-
-from PyQt5.QtWidgets import QMessageBox
from nw.core.tree import NWTree
from nw.core.item import NWItem
@@ -436,26 +434,15 @@ class NWProject():
xRoot = nwXML.getroot()
nwxRoot = xRoot.tag
- appVersion = "Unknown"
- hexVersion = "0x0"
- fileVersion = "Unknown"
- self.saveCount = 0
- self.autoCount = 0
-
- if "appVersion" in xRoot.attrib:
- appVersion = xRoot.attrib["appVersion"]
- if "hexVersion" in xRoot.attrib:
- hexVersion = xRoot.attrib["hexVersion"]
- if "fileVersion" in xRoot.attrib:
- fileVersion = xRoot.attrib["fileVersion"]
+ appVersion = xRoot.attrib.get("appVersion", "Unknown")
+ hexVersion = xRoot.attrib.get("hexVersion", "0x0")
+ fileVersion = xRoot.attrib.get("fileVersion", "Unknown")
# The following are deprecated and will be removed
- if "saveCount" in xRoot.attrib:
- self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False)
- if "autoCount" in xRoot.attrib:
- self.autoCount = checkInt(xRoot.attrib["autoCount"], 0, False)
- if "editTime" in xRoot.attrib:
- self.editTime = checkInt(xRoot.attrib["editTime"], 0, False)
+ # The settings have been moved to the tag
+ self.saveCount = checkInt(xRoot.attrib.get("saveCount", 0), 0, False)
+ self.autoCount = checkInt(xRoot.attrib.get("autoCount", 0), 0, False)
+ self.editTime = checkInt(xRoot.attrib.get("editTime", 0), 0, False)
logger.verbose("XML root is %s" % nwxRoot)
logger.verbose("File version is %s" % fileVersion)
@@ -483,20 +470,19 @@ class NWProject():
# parser will lose the autoReplace settings if allowed to
# read the file. Introduced in version 0.10.
- if fileVersion == "1.0" and self.mainConf.showGUI:
- msgBox = QMessageBox()
- msgRes = msgBox.question(self.theParent, "Old Project Version", (
+ if fileVersion == "1.0":
+ msgRes = self.theParent.askQuestion("Old Project Version", (
"The project file and data is created by a novelWriter version "
"lower than 0.7. Do you want to upgrade the project to the "
"most recent format?
Note that after the upgrade, you "
"cannot open the project with an older version of novelWriter "
"any more, so make sure you have a recent backup."
))
- if msgRes != QMessageBox.Yes:
+ if not msgRes:
self.clearProject()
return False
- elif fileVersion != "1.1" and fileVersion != "1.2" and self.mainConf.showGUI:
+ elif fileVersion != "1.1" and fileVersion != "1.2":
self.makeAlert((
"Unknown or unsupported novelWriter project file format. "
"The project cannot be opened by this version of novelWriter. "
@@ -510,9 +496,8 @@ class NWProject():
# Check novelWriter Version
# =========================
- if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI:
- msgBox = QMessageBox()
- msgRes = msgBox.question(self.theParent, "Version Conflict", (
+ if int(hexVersion, 16) > int(nw.__hexversion__, 16):
+ msgRes = self.theParent.askQuestion("Version Conflict", (
"This project was saved by a newer version of novelWriter, version %s. "
"This is version %s. If you continue to open the project, some attributes "
"and settings may not be preserved, but the overall project should be fine. "
@@ -520,7 +505,7 @@ class NWProject():
) % (
appVersion, nw.__version__
))
- if msgRes != QMessageBox.Yes:
+ if not msgRes:
self.clearProject()
return False
@@ -835,15 +820,15 @@ class NWProject():
try:
self._clearLockFile()
- make_archive(baseName, "zip", self.projPath, ".")
+ shutil.make_archive(baseName, "zip", self.projPath, ".")
self._writeLockFile()
+ logger.info("Backup written to: %s" % archName)
if doNotify:
self.theParent.makeAlert(
"Backup archive file written to: %s.zip" % os.path.join(cleanName, archName),
nwAlert.INFO
)
- else:
- logger.info("Backup written to: %s" % archName)
+
except Exception as e:
self.theParent.makeAlert(
["Could not write backup archive.", str(e)],
@@ -874,7 +859,7 @@ class NWProject():
self.setProjectPath(projPath, newProject=True)
try:
- unpack_archive(pkgSample, projPath)
+ shutil.unpack_archive(pkgSample, projPath)
isSuccess = True
except Exception as e:
self.makeAlert(
@@ -887,14 +872,14 @@ class NWProject():
try:
srcProj = os.path.join(srcSample, nwFiles.PROJ_FILE)
dstProj = os.path.join(projPath, nwFiles.PROJ_FILE)
- copyfile(srcProj, dstProj)
+ shutil.copyfile(srcProj, dstProj)
srcContent = os.path.join(srcSample, "content")
dstContent = os.path.join(projPath, "content")
for srcFile in os.listdir(srcContent):
srcDoc = os.path.join(srcContent, srcFile)
dstDoc = os.path.join(dstContent, srcFile)
- copyfile(srcDoc, dstDoc)
+ shutil.copyfile(srcDoc, dstDoc)
isSuccess = True
@@ -998,11 +983,15 @@ class NWProject():
"You must set a valid backup path in preferences to use "
"the automatic project backup feature."
), nwAlert.WARN)
+ return False
+
if self.projName == "":
self.theParent.makeAlert((
"You must set a valid project name in project settings to "
"use the automatic project backup feature."
), nwAlert.WARN)
+ return False
+
return True
def setSpellCheck(self, theMode):
@@ -1011,12 +1000,15 @@ class NWProject():
if self.spellCheck != theMode:
self.spellCheck = theMode
self.setProjectChanged(True)
- return True
+ return self.spellCheck
def setSpellLang(self, theLang):
"""Set the project-specific spell check language.
"""
- self.projLang = checkString(theLang, None, True)
+ theLang = checkString(theLang, None, True)
+ if self.projLang != theLang:
+ self.projLang = theLang
+ self.setProjectChanged(True)
return True
def setAutoOutline(self, theMode):
@@ -1025,7 +1017,7 @@ class NWProject():
if self.autoOutline != theMode:
self.autoOutline = theMode
self.setProjectChanged(True)
- return True
+ return self.autoOutline
def setTreeOrder(self, newOrder):
"""A list representing the linear/flattened order of project
@@ -1072,7 +1064,7 @@ class NWProject():
if nwItem.itemStatus in replaceMap.keys():
nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True)
- return
+ return True
def setImportColours(self, newCols):
"""Update the list of note file importance flags. Also iterate
@@ -1084,14 +1076,15 @@ class NWProject():
if nwItem.itemStatus in replaceMap.keys():
nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True)
- return
+ return True
def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary. This replaces the entire
dictionary, so alterations have to be made in a copy.
"""
self.autoReplace = autoReplace
- return
+ self.setProjectChanged(True)
+ return True
def setTitleFormat(self, titleFormat):
"""Set the formatting of titles in the project.
@@ -1099,7 +1092,7 @@ class NWProject():
for valKey, valEntry in titleFormat.items():
if valKey in self.titleFormat:
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey], False)
- return
+ return True
def setProjectChanged(self, bValue):
"""Toggle the project changed flag, and propagate the
@@ -1292,7 +1285,7 @@ class NWProject():
back into the project tree.
"""
if self.projPath is None:
- return
+ return False
# Then check the files in the data folder
logger.debug("Checking files in project content folder")
@@ -1352,7 +1345,7 @@ class NWProject():
orphItem.setLayout(oLayout)
self.projTree.append(oHandle, None, orphItem)
- return
+ return True
def _appendSessionStats(self):
"""Append session statistics to the sessions log file.
@@ -1490,7 +1483,8 @@ class NWProject():
os.unlink(rmFile)
except Exception as e:
logger.error(str(e))
+ return False
- return
+ return True
# END Class NWProject
diff --git a/nw/core/status.py b/nw/core/status.py
index 2c6fe811..ccec2e73 100644
--- a/nw/core/status.py
+++ b/nw/core/status.py
@@ -109,7 +109,8 @@ class NWStatus():
return
def countEntry(self, theLabel):
- """Lookup the usage count of a given entry.
+ """Increment the counter for a given label. This should be used
+ together with resetCounts in a loop over project items.
"""
theIndex = self.lookupEntry(theLabel)
if theIndex is not None:
diff --git a/nw/guimain.py b/nw/guimain.py
index 76b344fb..9efc9a2e 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -982,6 +982,13 @@ class GuiMain(QMainWindow):
return
+ def askQuestion(self, theTitle, theQuestion):
+ """Ask the user a Yes/No question.
+ """
+ msgBox = QMessageBox()
+ msgRes = msgBox.question(self, theTitle, theQuestion)
+ return msgRes == QMessageBox.Yes
+
def reportConfErr(self):
"""Checks if the Config module has any errors to report, and let
the user know if this is the case. The Config module caches
From c353e9ffb121196fdfffaee270454296a799073b Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 5 Dec 2020 21:06:57 +0100
Subject: [PATCH 19/22] Updated and new tests for NWProject class
---
nw/core/project.py | 51 +-
tests/README.md | 1 +
tests/conftest.py | 9 +-
tests/dummy.py | 20 +
tests/reference/coreProject_2_nwProject.nwx | 189 ++++-
tests/reference/coreProject_3_nwProject.nwx | 108 ++-
tests/reference/coreProject_4_nwProject.nwx | 189 +----
tests/reference/coreProject_5_nwProject.nwx | 108 +--
tests/test_core_project.py | 832 ++++++++++++++++++--
tests/test_core_tree.py | 6 +-
10 files changed, 1101 insertions(+), 412 deletions(-)
diff --git a/nw/core/project.py b/nw/core/project.py
index b646934a..1a834d73 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -1125,13 +1125,11 @@ class NWProject():
sentItems = []
iterItems = self.projTree.handles()
n = 0
- nMax = len(iterItems)
+ nMax = min(len(iterItems), 10000)
while n < nMax:
tHandle = iterItems[n]
tItem = self.projTree[tHandle]
n += 1
- if n > 10000:
- return # Just in case
if tItem is None:
# Technically a bug since treeOrder is built from the
# same data as projTree
@@ -1147,10 +1145,11 @@ class NWProject():
yield tItem
elif tItem.itemParent in iterItems:
# Item's parent exists, but hasn't been sent yet, so add
- # it again to the end
+ # it again to the end, but make sure this doesn't get
+ # out hand, so we cap at 10000 items
logger.warning("Item %s found before its parent" % tHandle)
iterItems.append(tHandle)
- nMax = len(iterItems)
+ nMax = min(len(iterItems), 10000)
else:
# Item is orphaned
logger.error("Item %s has no parent in current tree" % tHandle)
@@ -1189,13 +1188,12 @@ class NWProject():
if not os.path.isfile(lockFile):
return []
+ theLines = []
try:
with open(lockFile, mode="r", encoding="utf8") as inFile:
theData = inFile.read()
theLines = theData.splitlines()
- if len(theLines) == 4:
- return theLines
- else:
+ if len(theLines) != 4:
return ["ERROR"]
except Exception as e:
@@ -1203,7 +1201,7 @@ class NWProject():
logger.error(str(e))
return ["ERROR"]
- return ["ERROR"]
+ return theLines
def _writeLockFile(self):
"""Writes a lock file to the project folder.
@@ -1236,13 +1234,12 @@ class NWProject():
if os.path.isfile(lockFile):
try:
os.unlink(lockFile)
- return True
except Exception as e:
logger.error("Failed to remove project lockfile")
logger.error(str(e))
return False
- return None
+ return True
def _checkFolder(self, thePath):
"""Check if a folder exists, and if it doesn't, create it.
@@ -1356,21 +1353,27 @@ class NWProject():
sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS)
isFile = os.path.isfile(sessionFile)
- with open(sessionFile, mode="a+", encoding="utf8") as outFile:
- if not isFile:
- # It's a new file, so add a header
- if self.lastWCount > 0:
- outFile.write("# Offset %d\n" % self.lastWCount)
- outFile.write("# %-17s %-19s %8s %8s\n" % (
- "Start Time", "End Time", "Novel", "Notes"
+ try:
+ with open(sessionFile, mode="a+", encoding="utf8") as outFile:
+ if not isFile:
+ # It's a new file, so add a header
+ if self.lastWCount > 0:
+ outFile.write("# Offset %d\n" % self.lastWCount)
+ outFile.write("# %-17s %-19s %8s %8s\n" % (
+ "Start Time", "End Time", "Novel", "Notes"
+ ))
+
+ outFile.write("%-19s %-19s %8d %8d\n" % (
+ formatTimeStamp(self.projOpened),
+ formatTimeStamp(time()),
+ self.novelWCount,
+ self.notesWCount,
))
- outFile.write("%-19s %-19s %8d %8d\n" % (
- formatTimeStamp(self.projOpened),
- formatTimeStamp(time()),
- self.novelWCount,
- self.notesWCount,
- ))
+ except Exception as e:
+ logger.error("Failed to write session stats file")
+ logger.error(str(e))
+ return False
return True
diff --git a/tests/README.md b/tests/README.md
index 82a34712..cd1be55b 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -65,6 +65,7 @@ The commands for the respective test categories are listed below.
| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` |
| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` |
| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` |
+| Unit | NWProject class | nw/core/project.py | `-m core` | `-k testCoreProject` |
| Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
diff --git a/tests/conftest.py b/tests/conftest.py
index e6b90511..1f7b88ae 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,12 +6,15 @@ import sys
import pytest
import shutil
import os
+import time
from dummy import DummyMain
from PyQt5.QtWidgets import QMessageBox
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
+os.environ["TZ"] = "UTC"
+time.tzset()
from nw.config import Config # noqa: E402
@@ -69,7 +72,7 @@ def fncDir(tmpDir):
# novelWriter Objects
##
-@pytest.fixture(scope="session")
+@pytest.fixture(scope="function")
def tmpConf(tmpDir):
"""Create a temporary novelWriter configuration object.
"""
@@ -78,7 +81,7 @@ def tmpConf(tmpDir):
theConf.setLastPath("")
return theConf
-@pytest.fixture(scope="session")
+@pytest.fixture(scope="function")
def dummyGUI(tmpConf):
"""Create a dummy instance of novelWriter's main GUI class.
"""
@@ -168,6 +171,8 @@ def yesToAll(monkeypatch):
monkeypatch.setattr(
QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes
)
+ yield
+ monkeypatch.undo()
return
# =============================================================================================== #
diff --git a/tests/dummy.py b/tests/dummy.py
index 803e4e12..0f26e0ff 100644
--- a/tests/dummy.py
+++ b/tests/dummy.py
@@ -14,12 +14,22 @@ class DummyMain():
self.theIndex = None
self.theProject = None
self.statusBar = StatusBar()
+
+ # Test Variables
+ self.askResponse = True
+ self.lastAlert = ""
+
return
def makeAlert(self, theMessage, theLevel):
print("%s: %s" % (str(theLevel), theMessage))
+ self.lastAlert = str(theMessage)
return
+ def askQuestion(self, theTitle, theQustion):
+ print("Question: %s" % theQustion)
+ return self.askResponse
+
def setStatus(self, theMessage):
return
@@ -32,6 +42,16 @@ class DummyMain():
def rebuildIndex(self):
return
+ # Test Functions
+
+ def undo(self):
+ self.askResponse = True
+ return
+
+ def clear(self):
+ self.lastAlert = ""
+ return
+
# END Class GuiMain
class StatusBar():
diff --git a/tests/reference/coreProject_2_nwProject.nwx b/tests/reference/coreProject_2_nwProject.nwx
index 9ea0617f..fc7e7ab2 100644
--- a/tests/reference/coreProject_2_nwProject.nwx
+++ b/tests/reference/coreProject_2_nwProject.nwx
@@ -1,9 +1,11 @@
- New Project
-
- 2
+ Test Custom
+ Test Novel
+ Jane Doe
+ John Doh
+ 1
1
0
@@ -38,7 +40,7 @@
Main
-
+
-
Novel
ROOT
@@ -61,13 +63,34 @@
False
-
- World
+ Locations
ROOT
WORLD
New
False
- -
+
-
+ Timeline
+ ROOT
+ TIMELINE
+ New
+ False
+
+ -
+ Objects
+ ROOT
+ OBJECT
+ New
+ False
+
+ -
+ Entity
+ ROOT
+ ENTITY
+ New
+ False
+
+ -
Title Page
FILE
NOVEL
@@ -79,15 +102,15 @@
0
0
- -
- New Chapter
+
-
+ Chapter 1
FOLDER
NOVEL
New
False
- -
- New Chapter
+
-
+ Chapter 1
FILE
NOVEL
New
@@ -98,8 +121,8 @@
0
0
- -
- New Scene
+
-
+ Scene 1.1
FILE
NOVEL
New
@@ -110,33 +133,139 @@
0
0
- -
- Timeline
- ROOT
- TIMELINE
+
-
+ Scene 1.2
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 1.3
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Chapter 2
+ FOLDER
+ NOVEL
New
False
- -
- Object
- ROOT
- OBJECT
+
-
+ Chapter 2
+ FILE
+ NOVEL
+ New
+ True
+ CHAPTER
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 2.1
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 2.2
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 2.3
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Chapter 3
+ FOLDER
+ NOVEL
New
False
- -
- Custom1
- ROOT
- CUSTOM
+
-
+ Chapter 3
+ FILE
+ NOVEL
New
- False
+ True
+ CHAPTER
+ 0
+ 0
+ 0
+ 0
- -
- Custom2
- ROOT
- CUSTOM
+
-
+ Scene 3.1
+ FILE
+ NOVEL
New
- False
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 3.2
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 3.3
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
diff --git a/tests/reference/coreProject_3_nwProject.nwx b/tests/reference/coreProject_3_nwProject.nwx
index 66488434..11d007aa 100644
--- a/tests/reference/coreProject_3_nwProject.nwx
+++ b/tests/reference/coreProject_3_nwProject.nwx
@@ -1,9 +1,11 @@
-
+
- New Project
-
- 2
+ Test Custom
+ Test Novel
+ Jane Doe
+ John Doh
+ 1
1
0
@@ -38,7 +40,7 @@
Main
-
+
-
Novel
ROOT
@@ -61,13 +63,34 @@
False
-
- World
+ Locations
ROOT
WORLD
New
False
- -
+
-
+ Timeline
+ ROOT
+ TIMELINE
+ New
+ False
+
+ -
+ Objects
+ ROOT
+ OBJECT
+ New
+ False
+
+ -
+ Entity
+ ROOT
+ ENTITY
+ New
+ False
+
+ -
Title Page
FILE
NOVEL
@@ -79,27 +102,8 @@
0
0
- -
- New Chapter
- FOLDER
- NOVEL
- New
- False
-
- -
- New Chapter
- FILE
- NOVEL
- New
- True
- CHAPTER
- 0
- 0
- 0
- 0
-
- -
- New Scene
+
-
+ Scene 1
FILE
NOVEL
New
@@ -110,8 +114,8 @@
0
0
- -
- Hello
+
-
+ Scene 2
FILE
NOVEL
New
@@ -122,13 +126,49 @@
0
0
- -
- Jane
+
-
+ Scene 3
FILE
- CHARACTER
+ NOVEL
New
True
- NOTE
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 4
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 5
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
+ 0
+ 0
+ 0
+ 0
+
+ -
+ Scene 6
+ FILE
+ NOVEL
+ New
+ True
+ SCENE
0
0
0
diff --git a/tests/reference/coreProject_4_nwProject.nwx b/tests/reference/coreProject_4_nwProject.nwx
index fc7e7ab2..9ea0617f 100644
--- a/tests/reference/coreProject_4_nwProject.nwx
+++ b/tests/reference/coreProject_4_nwProject.nwx
@@ -1,11 +1,9 @@
- Test Custom
- Test Novel
- Jane Doe
- John Doh
- 1
+ New Project
+
+ 2
1
0
@@ -40,7 +38,7 @@
Main
-
+
-
Novel
ROOT
@@ -63,34 +61,13 @@
False
-
- Locations
+ World
ROOT
WORLD
New
False
- -
- Timeline
- ROOT
- TIMELINE
- New
- False
-
- -
- Objects
- ROOT
- OBJECT
- New
- False
-
- -
- Entity
- ROOT
- ENTITY
- New
- False
-
- -
+
-
Title Page
FILE
NOVEL
@@ -102,15 +79,15 @@
0
0
- -
- Chapter 1
+
-
+ New Chapter
FOLDER
NOVEL
New
False
- -
- Chapter 1
+
-
+ New Chapter
FILE
NOVEL
New
@@ -121,8 +98,8 @@
0
0
- -
- Scene 1.1
+
-
+ New Scene
FILE
NOVEL
New
@@ -133,139 +110,33 @@
0
0
- -
- Scene 1.2
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 1.3
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Chapter 2
- FOLDER
- NOVEL
+
-
+ Timeline
+ ROOT
+ TIMELINE
New
False
- -
- Chapter 2
- FILE
- NOVEL
- New
- True
- CHAPTER
- 0
- 0
- 0
- 0
-
- -
- Scene 2.1
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 2.2
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 2.3
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Chapter 3
- FOLDER
- NOVEL
+
-
+ Object
+ ROOT
+ OBJECT
New
False
- -
- Chapter 3
- FILE
- NOVEL
+
-
+ Custom1
+ ROOT
+ CUSTOM
New
- True
- CHAPTER
- 0
- 0
- 0
- 0
+ False
- -
- Scene 3.1
- FILE
- NOVEL
+
-
+ Custom2
+ ROOT
+ CUSTOM
New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 3.2
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 3.3
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
+ False
diff --git a/tests/reference/coreProject_5_nwProject.nwx b/tests/reference/coreProject_5_nwProject.nwx
index 11d007aa..66488434 100644
--- a/tests/reference/coreProject_5_nwProject.nwx
+++ b/tests/reference/coreProject_5_nwProject.nwx
@@ -1,11 +1,9 @@
-
+
- Test Custom
- Test Novel
- Jane Doe
- John Doh
- 1
+ New Project
+
+ 2
1
0
@@ -40,7 +38,7 @@
Main
-
+
-
Novel
ROOT
@@ -63,34 +61,13 @@
False
-
- Locations
+ World
ROOT
WORLD
New
False
- -
- Timeline
- ROOT
- TIMELINE
- New
- False
-
- -
- Objects
- ROOT
- OBJECT
- New
- False
-
- -
- Entity
- ROOT
- ENTITY
- New
- False
-
- -
+
-
Title Page
FILE
NOVEL
@@ -102,8 +79,27 @@
0
0
- -
- Scene 1
+
-
+ New Chapter
+ FOLDER
+ NOVEL
+ New
+ False
+
+ -
+ New Chapter
+ FILE
+ NOVEL
+ New
+ True
+ CHAPTER
+ 0
+ 0
+ 0
+ 0
+
+ -
+ New Scene
FILE
NOVEL
New
@@ -114,8 +110,8 @@
0
0
- -
- Scene 2
+
-
+ Hello
FILE
NOVEL
New
@@ -126,49 +122,13 @@
0
0
- -
- Scene 3
+
-
+ Jane
FILE
- NOVEL
+ CHARACTER
New
True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 4
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 5
- FILE
- NOVEL
- New
- True
- SCENE
- 0
- 0
- 0
- 0
-
- -
- Scene 6
- FILE
- NOVEL
- New
- True
- SCENE
+ NOTE
0
0
0
diff --git a/tests/test_core_project.py b/tests/test_core_project.py
index 6cf20033..869603fe 100644
--- a/tests/test_core_project.py
+++ b/tests/test_core_project.py
@@ -7,15 +7,18 @@ import os
from shutil import copyfile
from zipfile import ZipFile
+from lxml import etree
-from tools import cmpFiles
+from tools import cmpFiles, writeFile, readFile
+from dummy import causeOSError
from nw.core.project import NWProject
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
@pytest.mark.core
-def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
- """Test that a basic project can be created, opened and saved.
+def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
+ """Create a new project from a project wizard dictionary. With
+ default setting, creating a Minimal project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx")
@@ -29,7 +32,6 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
# Try again with a proper path
assert theProject.newProject({"projPath": fncDir})
- assert theProject.setProjectPath(fncDir)
assert theProject.saveProject()
assert theProject.closeProject()
@@ -59,72 +61,7 @@ def testCoreProject_NewOpenSave(fncDir, outDir, refDir, tmpDir, dummyGUI):
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
-# END Test testCoreProject_NewOpenSave
-
-@pytest.mark.core
-def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
- """Check that new root folders can be added to the project.
- """
- projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
-
- theProject = NWProject(dummyGUI)
- theProject.projTree.setSeed(42)
-
- assert theProject.newProject({"projPath": fncDir})
- assert theProject.setProjectPath(fncDir)
- assert theProject.saveProject()
- assert theProject.closeProject()
- assert theProject.openProject(projFile)
-
- assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None))
- assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None))
- assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None))
- assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), type(None))
- assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str)
- assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str)
- assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str)
- assert isinstance(theProject.newRoot("Custom2", nwItemClass.CUSTOM), str)
-
- assert theProject.projChanged
- assert theProject.saveProject()
- assert theProject.closeProject()
-
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
- assert not theProject.projChanged
-
-# END Test testCoreProject_NewRoot
-
-@pytest.mark.core
-def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
- """Check that new files can be added to the project.
- """
- projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
-
- theProject = NWProject(dummyGUI)
- theProject.projTree.setSeed(42)
-
- assert theProject.newProject({"projPath": fncDir})
- assert theProject.setProjectPath(fncDir)
- assert theProject.saveProject()
- assert theProject.closeProject()
- assert theProject.openProject(projFile)
-
- assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str)
- assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str)
- assert theProject.projChanged
- assert theProject.saveProject()
- assert theProject.closeProject()
-
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
- assert not theProject.projChanged
-
-# END Test testCoreProject_NewFile
+# END Test testCoreProject_NewMinimal
@pytest.mark.core
def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
@@ -132,8 +69,8 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
Custom type with chapters and scenes.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
projData = {
"projName": "Test Custom",
@@ -173,8 +110,8 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
Custom type without chapters, but with scenes.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
projData = {
"projName": "Test Custom",
@@ -296,7 +233,418 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
# END Test testCoreProject_NewSampleB
@pytest.mark.core
-def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
+def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
+ """Check that new root folders can be added to the project.
+ """
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx")
+
+ theProject = NWProject(dummyGUI)
+ theProject.projTree.setSeed(42)
+
+ assert theProject.newProject({"projPath": fncDir})
+ assert theProject.setProjectPath(fncDir)
+ assert theProject.saveProject()
+ assert theProject.closeProject()
+ assert theProject.openProject(projFile)
+
+ assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None))
+ assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None))
+ assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None))
+ assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), type(None))
+ assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str)
+ assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str)
+ assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str)
+ assert isinstance(theProject.newRoot("Custom2", nwItemClass.CUSTOM), str)
+
+ assert theProject.projChanged
+ assert theProject.saveProject()
+ assert theProject.closeProject()
+
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+ assert not theProject.projChanged
+
+# END Test testCoreProject_NewRoot
+
+@pytest.mark.core
+def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
+ """Check that new files can be added to the project.
+ """
+ projFile = os.path.join(fncDir, "nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx")
+
+ theProject = NWProject(dummyGUI)
+ theProject.projTree.setSeed(42)
+
+ assert theProject.newProject({"projPath": fncDir})
+ assert theProject.setProjectPath(fncDir)
+ assert theProject.saveProject()
+ assert theProject.closeProject()
+ assert theProject.openProject(projFile)
+
+ assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str)
+ assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str)
+ assert theProject.projChanged
+ assert theProject.saveProject()
+ assert theProject.closeProject()
+
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+ assert not theProject.projChanged
+
+# END Test testCoreProject_NewFile
+
+@pytest.mark.core
+def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
+ """Test opening a project.
+ """
+ theProject = NWProject(dummyGUI)
+
+ # Rename the project file to check handling
+ rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE)
+ wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj")
+ os.rename(rName, wName)
+ assert theProject.openProject(nwMinimal) is False
+ os.rename(wName, rName)
+
+ # Fail on folder structure check
+ monkeypatch.setattr("os.mkdir", causeOSError)
+ assert theProject.openProject(nwMinimal) is False
+ monkeypatch.undo()
+
+ # Fail on lock file
+ theProject.setProjectPath(nwMinimal)
+ assert theProject._writeLockFile()
+ assert theProject.openProject(nwMinimal) is False
+
+ # Fail to read lockfile (which still opens the project)
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert theProject.openProject(nwMinimal) is True
+ monkeypatch.undo()
+ assert theProject.closeProject()
+
+ # Force open with lockfile
+ theProject.setProjectPath(nwMinimal)
+ assert theProject._writeLockFile()
+ assert theProject.openProject(nwMinimal, overrideLock=True) is True
+ assert theProject.closeProject()
+
+ # Make a junk XML file
+ oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"orig")
+ bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"bak")
+ os.rename(rName, oName)
+ writeFile(rName, "dummy")
+ assert theProject.openProject(nwMinimal) is False
+
+ # Also write a jun XML backup file
+ writeFile(bName, "dummy")
+ assert theProject.openProject(nwMinimal) is False
+
+ # Wrong root item
+ writeFile(rName, "\n")
+ assert theProject.openProject(nwMinimal) is False
+
+ # Wrong file version
+ writeFile(rName, (
+ "\n"
+ "\n"
+ "\n"
+ ))
+ dummyGUI.askResponse = False
+ assert theProject.openProject(nwMinimal) is False
+ dummyGUI.undo()
+
+ # Future file version
+ writeFile(rName, (
+ "\n"
+ "\n"
+ "\n"
+ ))
+ assert theProject.openProject(nwMinimal) is False
+
+ # Larger hex version
+ writeFile(rName, (
+ "\n"
+ "\n"
+ "\n"
+ ))
+ dummyGUI.askResponse = False
+ assert theProject.openProject(nwMinimal) is False
+ dummyGUI.undo()
+
+ # Test skipping XML entries
+ writeFile(rName, (
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ ))
+ assert theProject.openProject(nwMinimal) is True
+ assert theProject.closeProject()
+
+ # Test deprecated XML entries
+ writeFile(rName, (
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "B\n"
+ "\n"
+ "\n"
+ "\n"
+ ))
+ assert theProject.openProject(nwMinimal) is True
+ assert theProject.autoReplace == {"A": "B"}
+ assert theProject.closeProject()
+
+ # Clean up XML files
+ os.unlink(rName)
+ os.unlink(bName)
+ os.rename(oName, rName)
+
+ # Add some legacy stuff that cannot be removed
+ writeFile(os.path.join(nwMinimal, "junk"), "dummy")
+ os.mkdir(os.path.join(nwMinimal, "data_0"))
+ writeFile(os.path.join(nwMinimal, "data_0", "junk"), "dummy")
+ dummyGUI.clear()
+ assert theProject.openProject(nwMinimal) is True
+ assert "data_0" in dummyGUI.lastAlert
+ assert theProject.closeProject()
+
+# END Test testCoreProject_Open
+
+@pytest.mark.core
+def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir):
+ """Test saving a project.
+ """
+ theProject = NWProject(dummyGUI)
+ testFile = os.path.join(nwMinimal, "nwProject.nwx")
+ compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx")
+
+ # Nothing to save
+ assert theProject.saveProject() is False
+
+ # Open test project
+ assert theProject.openProject(nwMinimal)
+
+ # Fail on folder structure check
+ monkeypatch.setattr("os.path.isdir", lambda *args: False)
+ assert theProject.saveProject() is False
+ monkeypatch.undo()
+
+ # Fail on open file
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert theProject.saveProject() is False
+ monkeypatch.undo()
+
+ # Successful save
+ saveCount = theProject.saveCount
+ autoCount = theProject.autoCount
+ assert theProject.saveProject() is True
+ assert theProject.saveCount == saveCount + 1
+ assert theProject.autoCount == autoCount
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9])
+
+ # Successful autosave
+ saveCount = theProject.saveCount
+ autoCount = theProject.autoCount
+ assert theProject.saveProject(autoSave=True) is True
+ assert theProject.saveCount == saveCount
+ assert theProject.autoCount == autoCount + 1
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 9])
+
+ # Close test project
+ assert theProject.closeProject()
+
+# END Test testCoreProject_Save
+
+@pytest.mark.core
+def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
+ """Test lock file functions for the project folder.
+ """
+ theProject = NWProject(dummyGUI)
+
+ lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK)
+
+ # No project
+ assert theProject._writeLockFile() is False
+ assert theProject._readLockFile() == ["ERROR"]
+ assert theProject._clearLockFile() is False
+
+ theProject.projPath = fncDir
+ theProject.mainConf.hostName = "TestHost"
+ theProject.mainConf.osType = "TestOS"
+ theProject.mainConf.kernelVer = "1.0"
+
+ # Block open
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert theProject._writeLockFile() is False
+ monkeypatch.undo()
+
+ # Write lock file
+ monkeypatch.setattr("nw.core.project.time", lambda: 123.4)
+ assert theProject._writeLockFile() is True
+ monkeypatch.undo()
+ assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n"
+
+ # Block open
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert theProject._readLockFile() == ["ERROR"]
+ monkeypatch.undo()
+
+ # Read lock file
+ assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"]
+
+ # Block unlink
+ monkeypatch.setattr("os.unlink", causeOSError)
+ assert os.path.isfile(lockFile)
+ assert theProject._clearLockFile() is False
+ assert os.path.isfile(lockFile)
+ monkeypatch.undo()
+
+ # Clear file
+ assert os.path.isfile(lockFile)
+ assert theProject._clearLockFile() is True
+ assert not os.path.isfile(lockFile)
+
+ # Read again, no file
+ assert theProject._readLockFile() == []
+
+ # Read an invalid lock file
+ writeFile(lockFile, "A\nB")
+ assert theProject._readLockFile() == ["ERROR"]
+ assert theProject._clearLockFile() is True
+
+# END Test testCoreProject_LockFile
+
+@pytest.mark.core
+def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
+ """Test helper functions for the project folder.
+ """
+ theProject = NWProject(dummyGUI)
+
+ # No path
+ assert theProject.ensureFolderStructure() is False
+
+ # Set the correct dir
+ theProject.projPath = fncDir
+
+ # Block user's home folder
+ monkeypatch.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir)
+ assert theProject.ensureFolderStructure() is False
+ monkeypatch.undo()
+
+ # Create a file to block meta folder
+ metaDir = os.path.join(fncDir, "meta")
+ writeFile(metaDir, "dummy")
+ assert theProject.ensureFolderStructure() is False
+ os.unlink(metaDir)
+
+ # Create a file to block cache folder
+ cacheDir = os.path.join(fncDir, "cache")
+ writeFile(cacheDir, "dummy")
+ assert theProject.ensureFolderStructure() is False
+ os.unlink(cacheDir)
+
+ # Create a file to block content folder
+ contentDir = os.path.join(fncDir, "content")
+ writeFile(contentDir, "dummy")
+ assert theProject.ensureFolderStructure() is False
+ os.unlink(contentDir)
+
+ # Now, do it right
+ assert theProject.ensureFolderStructure() is True
+ assert os.path.isdir(metaDir)
+ assert os.path.isdir(cacheDir)
+ assert os.path.isdir(contentDir)
+
+# END Test testCoreProject_Helpers
+
+@pytest.mark.core
+def testCoreProject_AccessItems(nwMinimal, dummyGUI):
+ """Test helper functions for the project folder.
+ """
+ theProject = NWProject(dummyGUI)
+ theProject.openProject(nwMinimal)
+
+ # Move Novel ROOT to after its files
+ oldOrder = [
+ "a508bb932959c", # ROOT: Novel
+ "a35baf2e93843", # FILE: Title Page
+ "a6d311a93600a", # FOLDER: New Chapter
+ "f5ab3e30151e1", # FILE: New Chapter
+ "8c659a11cd429", # FILE: New Scene
+ "7695ce551d265", # ROOT: Plot
+ "afb3043c7b2b3", # ROOT: Characters
+ "9d5247ab588e0", # ROOT: World
+ ]
+ newOrder = [
+ "a35baf2e93843", # FILE: Title Page
+ "f5ab3e30151e1", # FILE: New Chapter
+ "8c659a11cd429", # FILE: New Scene
+ "a6d311a93600a", # FOLDER: New Chapter
+ "a508bb932959c", # ROOT: Novel
+ "7695ce551d265", # ROOT: Plot
+ "afb3043c7b2b3", # ROOT: Characters
+ "9d5247ab588e0", # ROOT: World
+ ]
+ assert theProject.projTree.handles() == oldOrder
+ assert theProject.setTreeOrder(newOrder)
+ assert theProject.projTree.handles() == newOrder
+
+ # Add a non-existing item
+ theProject.projTree._treeOrder.append("01234567789abc")
+
+ # Add an item with a non-existent parent
+ nHandle = theProject.newFile("Test File", nwItemClass.NOVEL, "a6d311a93600a")
+ theProject.projTree[nHandle].setParent("cba9876543210")
+ assert theProject.projTree[nHandle].itemParent == "cba9876543210"
+
+ retOrder = []
+ for tItem in theProject.getProjectItems():
+ retOrder.append(tItem.itemHandle)
+
+ assert retOrder == [
+ "a508bb932959c", # ROOT: Novel
+ "7695ce551d265", # ROOT: Plot
+ "afb3043c7b2b3", # ROOT: Characters
+ "9d5247ab588e0", # ROOT: World
+ nHandle, # FILE: Test File
+ "a35baf2e93843", # FILE: Title Page
+ "a6d311a93600a", # FOLDER: New Chapter
+ "f5ab3e30151e1", # FILE: New Chapter
+ "8c659a11cd429", # FILE: New Scene
+ ]
+ assert theProject.projTree[nHandle].itemParent is None
+
+# END Test testCoreProject_AccessItems
+
+@pytest.mark.core
+def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
"""Test other project class methods and functions.
"""
theProject = NWProject(dummyGUI)
@@ -317,13 +665,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
assert theProject.setProjectPath(projPath, newProject=True)
# Make os.mkdir fail
- def altMkdir(*args):
- raise Exception("Oops!")
-
- monkeypatch.setattr("os.mkdir", altMkdir)
+ monkeypatch.setattr("os.mkdir", causeOSError)
projPath = os.path.join(nwMinimal, "dummy2")
assert not theProject.setProjectPath(projPath, newProject=True)
+ # Set back
+ assert theProject.setProjectPath(nwMinimal)
+
# Project Name
assert theProject.setProjectName(" A Name ")
assert theProject.projName == "A Name"
@@ -337,6 +685,191 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI):
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
+ # Trash folder
+ # Should create on first call, and just returned on later calls
+ assert theProject.projTree["73475cb40a568"] is None
+ assert theProject.trashFolder() == "73475cb40a568"
+ assert theProject.trashFolder() == "73475cb40a568"
+
+ # Project backup
+ assert theProject.doBackup is True
+ assert theProject.setProjBackup(False)
+ assert theProject.doBackup is False
+
+ assert not theProject.setProjBackup(True)
+ theProject.mainConf.backupPath = tmpDir
+ assert theProject.setProjBackup(True)
+
+ assert theProject.setProjectName("")
+ assert not theProject.setProjBackup(True)
+ assert theProject.setProjectName("A Name")
+ assert theProject.setProjBackup(True)
+
+ # Spell check
+ theProject.projChanged = False
+ assert theProject.setSpellCheck(True)
+ assert not theProject.setSpellCheck(False)
+ assert theProject.projChanged
+
+ # Spell language
+ theProject.projChanged = False
+ assert theProject.setSpellLang(None)
+ assert theProject.projLang is None
+ assert theProject.setSpellLang("None")
+ assert theProject.projLang is None
+ assert theProject.setSpellLang("en_GB")
+ assert theProject.projLang == "en_GB"
+ assert theProject.projChanged
+
+ # Automatic outline update
+ theProject.projChanged = False
+ assert theProject.setAutoOutline(True)
+ assert not theProject.setAutoOutline(False)
+ assert theProject.projChanged
+
+ # Last edited
+ theProject.projChanged = False
+ assert theProject.setLastEdited("0123456789abc")
+ assert theProject.lastEdited == "0123456789abc"
+ assert theProject.projChanged
+
+ # Last viewed
+ theProject.projChanged = False
+ assert theProject.setLastViewed("0123456789abc")
+ assert theProject.lastViewed == "0123456789abc"
+ assert theProject.projChanged
+
+ # Autoreplace
+ theProject.projChanged = False
+ assert theProject.setAutoReplace({"A": "B", "C": "D"})
+ assert theProject.autoReplace == {"A": "B", "C": "D"}
+ assert theProject.projChanged
+
+ # Change project tree order
+ oldOrder = [
+ "a508bb932959c", "a35baf2e93843", "a6d311a93600a",
+ "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
+ "afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568",
+ ]
+ newOrder = [
+ "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
+ "a508bb932959c", "a35baf2e93843", "a6d311a93600a",
+ "afb3043c7b2b3", "9d5247ab588e0",
+ ]
+ assert theProject.projTree.handles() == oldOrder
+ assert theProject.setTreeOrder(newOrder)
+ assert theProject.projTree.handles() == newOrder
+ assert theProject.setTreeOrder(oldOrder)
+ assert theProject.projTree.handles() == oldOrder
+
+ # Change status
+ theProject.projTree["a35baf2e93843"].setStatus("Finished")
+ theProject.projTree["a6d311a93600a"].setStatus("Draft")
+ theProject.projTree["f5ab3e30151e1"].setStatus("Note")
+ theProject.projTree["8c659a11cd429"].setStatus("Finished")
+ newList = [
+ ("New", 1, 1, 1, "New"),
+ ("Draft", 2, 2, 2, "Note"), # These are swapped
+ ("Note", 3, 3, 3, "Draft"), # These are swapped
+ ("Edited", 4, 4, 4, "Finished"), # Renamed
+ ("Finished", 5, 5, 5, None), # New, with reused name
+ ]
+ assert theProject.setStatusColours(newList)
+ assert theProject.statusItems._theLabels == [
+ "New", "Draft", "Note", "Edited", "Finished"
+ ]
+ assert theProject.statusItems._theColours == [
+ (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
+ ]
+ assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
+ assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
+ assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
+ assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
+
+ # Change importance
+ fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
+ theProject.projTree[fHandle].setStatus("Main")
+ newList = [
+ ("New", 1, 1, 1, "New"),
+ ("Minor", 2, 2, 2, "Minor"),
+ ("Major", 3, 3, 3, "Major"),
+ ("Min", 4, 4, 4, "Main"),
+ ("Max", 5, 5, 5, None),
+ ]
+ assert theProject.setImportColours(newList)
+ assert theProject.importItems._theLabels == [
+ "New", "Minor", "Major", "Min", "Max"
+ ]
+ assert theProject.importItems._theColours == [
+ (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
+ ]
+ assert theProject.projTree[fHandle].itemStatus == "Min"
+
+ # Check status counts
+ assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0]
+ assert theProject.importItems._theCounts == [0, 0, 0, 0, 0]
+ theProject.countStatus()
+ assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
+ assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]
+
+ # Check word counts
+ theProject.currWCount = 200
+ theProject.lastWCount = 100
+ assert theProject.getSessionWordCount() == 100
+
+ # Session stats
+ monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False)
+ assert not theProject._appendSessionStats()
+ monkeypatch.undo()
+
+ # Block open
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert not theProject._appendSessionStats()
+ monkeypatch.undo()
+
+ # Write entry
+ assert theProject.projMeta == os.path.join(nwMinimal, "meta")
+ statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
+
+ theProject.projOpened = 1600002000
+ theProject.novelWCount = 200
+ theProject.notesWCount = 100
+
+ monkeypatch.setattr("nw.core.project.time", lambda: 1600005600)
+ assert theProject._appendSessionStats()
+ monkeypatch.undo()
+
+ assert readFile(statsFile) == (
+ "# Offset 100\n"
+ "# Start Time End Time Novel Notes\n"
+ "2020-09-13 13:00:00 2020-09-13 14:00:00 200 100\n"
+ )
+
+ # Pack XML Value
+ xElem = etree.Element("element")
+ theProject._packProjectValue(xElem, "A", "B", allowNone=False)
+ assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
+ b"B"
+ )
+
+ xElem = etree.Element("element")
+ theProject._packProjectValue(xElem, "A", "", allowNone=False)
+ assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
+ b""
+ )
+
+ # Pack XML Key/Value
+ xElem = etree.Element("element")
+ theProject._packProjectKeyValue(xElem, "item", {"A": "B", "C": "D"})
+ assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
+ b""
+ b"
- "
+ b"B"
+ b"D"
+ b"
"
+ b""
+ )
+
# END Test testCoreProject_Methods
@pytest.mark.core
@@ -347,6 +880,7 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
the meta line at the top of the document file.
"""
theProject = NWProject(dummyGUI)
+
assert theProject.openProject(nwLipsum)
assert theProject.projTree["636b6aa9b697b"] is None
assert theProject.closeProject()
@@ -408,6 +942,10 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
assert theProject.saveProject(nwLipsum)
assert theProject.closeProject()
+ # Finally, check that the orphaned files function returns
+ # if no project is open and no path is set
+ assert not theProject._scanProjectFolder()
+
# END Test testCoreProject_OrphanedFiles
@pytest.mark.core
@@ -418,7 +956,6 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
contained in a single 'content' folder.
"""
theProject = NWProject(dummyGUI)
- theProject.mainConf.showGUI = False
# Create dummy files for known legacy files
deleteFiles = [
@@ -451,8 +988,7 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
# Create dummy files
os.mkdir(os.path.join(nwOldProj, "cache"))
for aFile in deleteFiles:
- with open(aFile, mode="w+", encoding="utf8") as outFile:
- outFile.write("Hi")
+ writeFile(aFile, "Hi")
for aFile in deleteFiles:
assert os.path.isfile(aFile)
@@ -503,7 +1039,112 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
# END Test testCoreProject_OldFormat
@pytest.mark.core
-def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
+def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
+ """Test the functins that handle legacy data folders and structure
+ with additional tests of failure handling.
+ """
+ theProject = NWProject(dummyGUI)
+ theProject.setProjectPath(fncDir)
+
+ # assert theProject.newProject({"projPath": fncDir})
+ # assert theProject.saveProject()
+ # assert theProject.closeProject()
+
+ # Check behaviour of deprecated files function on OSError
+ tstFile = os.path.join(fncDir, "ToC.json")
+ writeFile(tstFile, "dummy")
+ assert os.path.isfile(tstFile)
+
+ monkeypatch.setattr("os.unlink", causeOSError)
+ assert not theProject._deprecatedFiles()
+ monkeypatch.undo()
+
+ assert theProject._deprecatedFiles()
+ assert not os.path.isfile(tstFile)
+
+ # Check processing non-folders
+ tstFile = os.path.join(fncDir, "data_0")
+ writeFile(tstFile, "dummy")
+ assert os.path.isfile(tstFile)
+
+ errList = []
+ errList = theProject._legacyDataFolder(tstFile, errList)
+ assert len(errList) > 0
+
+ # Move folder in data folder, shouldn't be there
+ tstData = os.path.join(fncDir, "data_1")
+ errItem = os.path.join(fncDir, "data_1", "stuff")
+ os.mkdir(tstData)
+ os.mkdir(errItem)
+ assert os.path.isdir(tstData)
+ assert os.path.isdir(errItem)
+
+ # This causes a failure to create the 'junk' folder
+ monkeypatch.setattr("os.mkdir", causeOSError)
+ errList = []
+ errList = theProject._legacyDataFolder(tstData, errList)
+ assert len(errList) > 0
+ monkeypatch.undo()
+
+ # This causes a failure to move 'stuff' to 'junk'
+ monkeypatch.setattr("os.rename", causeOSError)
+ errList = []
+ errList = theProject._legacyDataFolder(tstData, errList)
+ assert len(errList) > 0
+ monkeypatch.undo()
+
+ # This should be successful
+ errList = []
+ errList = theProject._legacyDataFolder(tstData, errList)
+ assert len(errList) == 0
+ assert os.path.isdir(os.path.join(fncDir, "junk", "stuff"))
+
+ # Check renaming/deleting of old document files
+ tstData = os.path.join(fncDir, "data_2")
+ tstDoc1m = os.path.join(tstData, "000000000001_main.nwd")
+ tstDoc1b = os.path.join(tstData, "000000000001_main.bak")
+ tstDoc2m = os.path.join(tstData, "000000000002_main.nwd")
+ tstDoc2b = os.path.join(tstData, "000000000002_main.bak")
+ tstDoc3m = os.path.join(tstData, "tooshort003_main.nwd")
+ tstDoc3b = os.path.join(tstData, "tooshort003_main.bak")
+
+ os.mkdir(tstData)
+ writeFile(tstDoc1m, "dummy")
+ writeFile(tstDoc1b, "dummy")
+ writeFile(tstDoc2m, "dummy")
+ writeFile(tstDoc2b, "dummy")
+ writeFile(tstDoc3m, "dummy")
+ writeFile(tstDoc3b, "dummy")
+
+ # Make the above fail
+ monkeypatch.setattr("os.rename", causeOSError)
+ monkeypatch.setattr("os.unlink", causeOSError)
+ errList = []
+ errList = theProject._legacyDataFolder(tstData, errList)
+ assert len(errList) > 0
+ assert os.path.isfile(tstDoc1m)
+ assert os.path.isfile(tstDoc1b)
+ assert os.path.isfile(tstDoc2m)
+ assert os.path.isfile(tstDoc2b)
+ assert os.path.isfile(tstDoc3m)
+ assert os.path.isfile(tstDoc3b)
+ monkeypatch.undo()
+
+ # And succeed ...
+ errList = []
+ errList = theProject._legacyDataFolder(tstData, errList)
+ assert len(errList) == 0
+
+ assert not os.path.isdir(tstData)
+ assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd"))
+ assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd"))
+ assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.nwd"))
+ assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.bak"))
+
+# END Test testCoreProject_LegacyData
+
+@pytest.mark.core
+def testCoreProject_Backup(monkeypatch, dummyGUI, nwMinimal, tmpDir):
"""Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to
@@ -513,6 +1154,12 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
assert theProject.openProject(nwMinimal)
# Test faulty settings
+
+ # No project
+ dummyGUI.hasProject = False
+ assert not theProject.zipIt(doNotify=False)
+ dummyGUI.hasProject = True
+
# Invalid path
theProject.mainConf.backupPath = None
assert not theProject.zipIt(doNotify=False)
@@ -531,9 +1178,21 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
theProject.mainConf.backupPath = nwMinimal
assert not theProject.zipIt(doNotify=False)
- # Test correct settings
+ # Set a valid folder
theProject.mainConf.backupPath = tmpDir
- assert theProject.zipIt(doNotify=False)
+
+ # Can't make folder
+ monkeypatch.setattr("os.mkdir", causeOSError)
+ assert not theProject.zipIt(doNotify=False)
+ monkeypatch.undo()
+
+ # Can't write archive
+ monkeypatch.setattr("shutil.make_archive", causeOSError)
+ assert not theProject.zipIt(doNotify=False)
+ monkeypatch.undo()
+
+ # Test correct settings
+ assert theProject.zipIt(doNotify=True)
theFiles = os.listdir(os.path.join(tmpDir, "Test Minimal"))
assert len(theFiles) == 1
@@ -548,7 +1207,8 @@ def testCoreProject_Backup(dummyGUI, nwMinimal, tmpDir):
# Check that the main project file was restored
assert cmpFiles(
- os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx")
+ os.path.join(nwMinimal, "nwProject.nwx"),
+ os.path.join(tmpDir, "extract", "nwProject.nwx")
)
# END Test testCoreProject_Backup
diff --git a/tests/test_core_tree.py b/tests/test_core_tree.py
index f417fd6a..29c1ca95 100644
--- a/tests/test_core_tree.py
+++ b/tests/test_core_tree.py
@@ -10,7 +10,7 @@ from lxml import etree
from nw.core.project import NWProject, NWItem, NWTree
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
-@pytest.fixture(scope="session")
+@pytest.fixture(scope="function")
def dummyItems(dummyGUI):
"""Create a list of dummy items.
"""
@@ -353,7 +353,7 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
b"True "
b"- "
b"Chapter OneFILENOVELNone"
- b"TrueUNNUMBERED300"
+ b"TrueCHAPTER300"
b"5020
"
b"- "
b"Scene OneFILENOVELNone"
@@ -425,7 +425,7 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir):
"\n"
"File Name Class Layout Document Label\n"
"-------------------------------------------------------------\n"
- f"{pathA} NOVEL UNNUMBERED Chapter One\n"
+ f"{pathA} NOVEL CHAPTER Chapter One\n"
f"{pathB} NOVEL SCENE Scene One\n"
f"{pathC} CHARACTER NOTE Jane Doe\n"
)
From d274c418d141d51e1cbb45248c39963ce79dca77 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 5 Dec 2020 21:08:42 +0100
Subject: [PATCH 20/22] Remove nwFuncTemp fixture
---
tests/conftest.py | 14 -------------
tests/test_dialogs.py | 40 ++++++++++++++++++-------------------
tests/test_error.py | 4 ++--
tests/test_gui.py | 46 +++++++++++++++++++++----------------------
4 files changed, 45 insertions(+), 59 deletions(-)
diff --git a/tests/conftest.py b/tests/conftest.py
index 1f7b88ae..5195b841 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -198,17 +198,3 @@ def nwTempBuild(tmpDir):
if not os.path.isdir(buildDir):
os.mkdir(buildDir)
return buildDir
-
-@pytest.fixture(scope="function")
-def nwFuncTemp(tmpDir):
- """A temporary folder for a single test function.
- """
- funcDir = os.path.join(tmpDir, "ftemp")
- if os.path.isdir(funcDir):
- shutil.rmtree(funcDir)
- if not os.path.isdir(funcDir):
- os.mkdir(funcDir)
- yield funcDir
- if os.path.isdir(funcDir):
- shutil.rmtree(funcDir)
- return
diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py
index b8e3b276..96f64da4 100644
--- a/tests/test_dialogs.py
+++ b/tests/test_dialogs.py
@@ -30,7 +30,7 @@ typeDelay = 1
stepDelay = 20
@pytest.mark.gui
-def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
+def testProjectSettings(qtbot, monkeypatch, yesToAll, fncDir, nwTempGUI, refDir, tmpDir):
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
nwGUI.show()
@@ -43,8 +43,8 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, ref
# Create new project
nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp})
- nwGUI.mainConf.backupPath = nwFuncTemp
+ assert nwGUI.newProject({"projPath": fncDir})
+ nwGUI.mainConf.backupPath = fncDir
# Get the dialog object
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None)
@@ -135,7 +135,7 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, ref
qtbot.wait(stepDelay)
# Check the files
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(nwTempGUI, "2_nwProject.nwx")
refFile = os.path.join(refDir, "gui", "2_nwProject.nwx")
copyfile(projFile, testFile)
@@ -145,7 +145,7 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, ref
nwGUI.closeMain()
@pytest.mark.gui
-def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir, tmpDir):
+def testItemEditor(qtbot, yesToAll, monkeypatch, fncDir, nwTempGUI, refDir, tmpDir):
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
nwGUI.show()
@@ -154,7 +154,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir,
# Create new, save, open project
nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp})
+ assert nwGUI.newProject({"projPath": fncDir})
assert nwGUI.openDocument("0e17daca5f3e1")
assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True)
@@ -208,7 +208,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir,
qtbot.wait(stepDelay)
# Check the files
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(nwTempGUI, "3_nwProject.nwx")
refFile = os.path.join(refDir, "gui", "3_nwProject.nwx")
copyfile(projFile, testFile)
@@ -218,7 +218,7 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, refDir,
nwGUI.closeMain()
@pytest.mark.gui
-def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
+def testWritingStatsExport(qtbot, monkeypatch, yesToAll, fncDir, tmpDir):
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
nwGUI.show()
@@ -227,13 +227,13 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
# Create new, save, close project
nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp})
+ assert nwGUI.newProject({"projPath": fncDir})
qtbot.wait(200)
assert nwGUI.saveProject()
assert nwGUI.closeProject()
qtbot.wait(stepDelay)
- sessFile = os.path.join(nwFuncTemp, "meta", nwFiles.SESS_STATS)
+ sessFile = os.path.join(fncDir, "meta", nwFiles.SESS_STATS)
with open(sessFile, mode="w+", encoding="utf-8") as outFile:
outFile.write(
"# Start Time End Time Novel Notes\n"
@@ -244,10 +244,10 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
)
# Open again, and check the stats
- assert nwGUI.openProject(nwFuncTemp)
+ assert nwGUI.openProject(fncDir)
qtbot.wait(stepDelay)
- nwGUI.mainConf.lastPath = nwFuncTemp
+ nwGUI.mainConf.lastPath = fncDir
nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
@@ -264,7 +264,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(100)
- jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.load(inFile)
@@ -282,7 +282,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
- jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
@@ -299,7 +299,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
- jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.load(inFile)
@@ -316,7 +316,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
- jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.load(inFile)
@@ -329,7 +329,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
- jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.load(inFile)
@@ -341,7 +341,7 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
- jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.load(inFile)
@@ -357,8 +357,8 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, tmpDir):
nwGUI.closeMain()
@pytest.mark.gui
-def testAboutBox(qtbot, monkeypatch, nwFuncTemp, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir])
+def testAboutBox(qtbot, monkeypatch, fncDir, tmpDir):
+ nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
diff --git a/tests/test_error.py b/tests/test_error.py
index 7ad07269..192fb6cb 100644
--- a/tests/test_error.py
+++ b/tests/test_error.py
@@ -10,9 +10,9 @@ from PyQt5.QtWidgets import qApp
from nw.error import NWErrorMessage, exceptionHandler
@pytest.mark.error
-def testErrorDialog(qtbot, nwFuncTemp, tmpDir):
+def testErrorDialog(qtbot, fncDir, tmpDir):
qApp.closeAllWindows()
- nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir])
+ nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
diff --git a/tests/test_gui.py b/tests/test_gui.py
index a6438d0a..a10c0582 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -27,11 +27,11 @@ typeDelay = 1
stepDelay = 20
@pytest.mark.gui
-def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
+def testLaunch(qtbot, monkeypatch, fncDir, tmpDir):
# Defaults
nwGUI = nw.main(
- ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir, "--style=Fusion"]
+ ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir, "--style=Fusion"]
)
assert nw.logger.getEffectiveLevel() == logging.WARNING
nwGUI.closeMain()
@@ -39,21 +39,21 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
# Log Levels
nwGUI = nw.main(
- ["--testmode", "--info", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
assert nw.logger.getEffectiveLevel() == logging.INFO
nwGUI.closeMain()
nwGUI.close()
nwGUI = nw.main(
- ["--testmode", "--debug", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--debug", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
assert nw.logger.getEffectiveLevel() == logging.DEBUG
nwGUI.closeMain()
nwGUI.close()
nwGUI = nw.main(
- ["--testmode", "--verbose", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--verbose", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
assert nw.logger.getEffectiveLevel() == 5
nwGUI.closeMain()
@@ -62,7 +62,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
# Help and Version
with pytest.raises(SystemExit) as ex:
nwGUI = nw.main(
- ["--testmode", "--help", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--help", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
nwGUI.closeMain()
nwGUI.close()
@@ -70,7 +70,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
with pytest.raises(SystemExit) as ex:
nwGUI = nw.main(
- ["--testmode", "--version", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--version", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
nwGUI.closeMain()
nwGUI.close()
@@ -79,7 +79,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
# Invalid options
with pytest.raises(SystemExit) as ex:
nwGUI = nw.main(
- ["--testmode", "--invalid", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--invalid", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
nwGUI.closeMain()
nwGUI.close()
@@ -92,7 +92,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000)
with pytest.raises(SystemExit) as ex:
nwGUI = nw.main(
- ["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % tmpDir]
+ ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]
)
nwGUI.closeMain()
nwGUI.close()
@@ -103,7 +103,7 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, tmpDir):
monkeypatch.undo()
@pytest.mark.gui
-def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
+def testDocEditor(qtbot, yesToAll, fncDir, nwTempGUI, refDir, tmpDir):
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
@@ -113,7 +113,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
# Create new, save, close project
nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp})
+ assert nwGUI.newProject({"projPath": fncDir})
assert nwGUI.saveProject()
assert nwGUI.closeProject()
@@ -130,7 +130,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
assert not nwGUI.theProject.spellCheck
# Check the files
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(nwTempGUI, "0_nwProject.nwx")
refFile = os.path.join(refDir, "gui", "0_nwProject.nwx")
copyfile(projFile, testFile)
@@ -140,7 +140,7 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
# qtbot.stopForInteraction()
# Re-open project
- assert nwGUI.openProject(nwFuncTemp)
+ assert nwGUI.openProject(fncDir)
qtbot.wait(stepDelay)
# Check that we loaded the data
@@ -148,8 +148,8 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
assert len(nwGUI.theProject.projTree._treeOrder) == 8
assert len(nwGUI.theProject.projTree._treeRoots) == 4
assert nwGUI.theProject.projTree.trashRoot() is None
- assert nwGUI.theProject.projPath == nwFuncTemp
- assert nwGUI.theProject.projMeta == os.path.join(nwFuncTemp, "meta")
+ assert nwGUI.theProject.projPath == fncDir
+ assert nwGUI.theProject.projMeta == os.path.join(fncDir, "meta")
assert nwGUI.theProject.projFile == "nwProject.nwx"
assert nwGUI.theProject.projName == "New Project"
assert nwGUI.theProject.bookTitle == ""
@@ -379,31 +379,31 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, refDir, tmpDir):
assert nwGUI.saveProject()
# Check the files
- projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
+ projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(nwTempGUI, "1_nwProject.nwx")
refFile = os.path.join(refDir, "gui", "1_nwProject.nwx")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
- projFile = os.path.join(nwFuncTemp, "content", "031b4af5197ec.nwd")
+ projFile = os.path.join(fncDir, "content", "031b4af5197ec.nwd")
testFile = os.path.join(nwTempGUI, "1_031b4af5197ec.nwd")
refFile = os.path.join(refDir, "gui", "1_031b4af5197ec.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
- projFile = os.path.join(nwFuncTemp, "content", "1a6562590ef19.nwd")
+ projFile = os.path.join(fncDir, "content", "1a6562590ef19.nwd")
testFile = os.path.join(nwTempGUI, "1_1a6562590ef19.nwd")
refFile = os.path.join(refDir, "gui", "1_1a6562590ef19.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
- projFile = os.path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd")
+ projFile = os.path.join(fncDir, "content", "0e17daca5f3e1.nwd")
testFile = os.path.join(nwTempGUI, "1_0e17daca5f3e1.nwd")
refFile = os.path.join(refDir, "gui", "1_0e17daca5f3e1.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
- projFile = os.path.join(nwFuncTemp, "content", "41cfc0d1f2d12.nwd")
+ projFile = os.path.join(fncDir, "content", "41cfc0d1f2d12.nwd")
testFile = os.path.join(nwTempGUI, "1_41cfc0d1f2d12.nwd")
refFile = os.path.join(refDir, "gui", "1_41cfc0d1f2d12.nwd")
copyfile(projFile, testFile)
@@ -1013,7 +1013,7 @@ def testContextMenu(qtbot, yesToAll, nwLipsum, tmpDir):
nwGUI.close()
@pytest.mark.gui
-def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir):
+def testInsertMenu(qtbot, monkeypatch, fncDir, tmpDir):
nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
qtbot.addWidget(nwGUI)
nwGUI.show()
@@ -1021,7 +1021,7 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir):
qtbot.wait(stepDelay)
nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp})
+ assert nwGUI.newProject({"projPath": fncDir})
assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
@@ -1205,7 +1205,7 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, tmpDir):
assert len(theBits) == 3
assert theBits[0] == "File details for the currently open file"
assert theBits[1] == "Handle: 0e17daca5f3e1"
- assert theBits[2] == "Location: %s" % os.path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd")
+ assert theBits[2] == "Location: %s" % os.path.join(fncDir, "content", "0e17daca5f3e1.nwd")
# qtbot.stopForInteraction()
nwGUI.closeMain()
From 51cb9814d2f035622a983c22a57f03dd7874d53d Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 5 Dec 2020 21:10:34 +0100
Subject: [PATCH 21/22] Rename GUI test files
---
tests/{test_dialogs.py => test_gui_dialogs.py} | 0
tests/{test_gui.py => test_gui_main.py} | 0
2 files changed, 0 insertions(+), 0 deletions(-)
rename tests/{test_dialogs.py => test_gui_dialogs.py} (100%)
rename tests/{test_gui.py => test_gui_main.py} (100%)
diff --git a/tests/test_dialogs.py b/tests/test_gui_dialogs.py
similarity index 100%
rename from tests/test_dialogs.py
rename to tests/test_gui_dialogs.py
diff --git a/tests/test_gui.py b/tests/test_gui_main.py
similarity index 100%
rename from tests/test_gui.py
rename to tests/test_gui_main.py
From bd9ed0d5d3c003b13692d87871fa513eff0cd6e6 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 5 Dec 2020 21:24:19 +0100
Subject: [PATCH 22/22] Fix testCoreProject_Methods test on Windows
---
tests/conftest.py | 3 ---
tests/test_core_project.py | 5 +++--
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/tests/conftest.py b/tests/conftest.py
index 5195b841..0d66d0ea 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,15 +6,12 @@ import sys
import pytest
import shutil
import os
-import time
from dummy import DummyMain
from PyQt5.QtWidgets import QMessageBox
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
-os.environ["TZ"] = "UTC"
-time.tzset()
from nw.config import Config # noqa: E402
diff --git a/tests/test_core_project.py b/tests/test_core_project.py
index 869603fe..fe847983 100644
--- a/tests/test_core_project.py
+++ b/tests/test_core_project.py
@@ -14,6 +14,7 @@ from dummy import causeOSError
from nw.core.project import NWProject
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
+from nw.common import formatTimeStamp
@pytest.mark.core
def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
@@ -842,8 +843,8 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
assert readFile(statsFile) == (
"# Offset 100\n"
"# Start Time End Time Novel Notes\n"
- "2020-09-13 13:00:00 2020-09-13 14:00:00 200 100\n"
- )
+ "%s %s 200 100\n"
+ ) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600))
# Pack XML Value
xElem = etree.Element("element")