Fix and optimise code (#904)

* Fix bug in early error reporting in main init
* Update docstrings and optimise code in GuiMain
* Update docstrings and optimise code in Config
* Update docstrings and optimise code in common module
* Update docstrings and optimise code in main project classes
* Update the OptionState class
* Update the spell checker class
* Update the file converter classes and extend tests
* Update the about, merge, split and item editor classes and extend tests
* Update item editor test
* Update about dialog tests
* Some minor test cleanup
* Fix typo and add clarification in contributing guide
This commit is contained in:
Veronica Berglyd Olsen
2021-10-14 20:35:42 +01:00
committed by GitHub
parent 2a06db0a2b
commit 1c45331b0a
31 changed files with 1042 additions and 666 deletions
@@ -1,115 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.5-alpha0" hexVersion="0x010500a0" fileVersion="1.2" timeStamp="2021-08-02 03:04:11">
<project>
<name>New Project</name>
<title></title>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<autoOutline>True</autoOutline>
<lastEdited>0e17daca5f3e1</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>6</lastWordCount>
<novelWordCount>6</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
<entry blue="0" green="150" red="200">Draft</entry>
<entry blue="0" green="200" red="50">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Minor</entry>
<entry blue="0" green="150" red="200">Major</entry>
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="8">
<item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="1" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="1" parent="31489056e0916">
<name>Just a Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Note</status>
<exported>False</exported>
<layout>DOCUMENT</layout>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="811786ad1ae74" order="3" parent="None">
<name>World</name>
<type>ROOT</type>
<class>WORLD</class>
<status>New</status>
<expanded>False</expanded>
</item>
</content>
</novelWriterXML>
+16 -2
View File
@@ -31,8 +31,8 @@ from tools import writeFile
from novelwriter.guimain import GuiMain
from novelwriter.common import (
checkString, checkInt, checkBool, checkHandle, isHandle, isTitleTag,
isItemClass, isItemType, isItemLayout, hexToInt, formatInt,
checkString, checkInt, checkFloat, checkBool, checkHandle, isHandle,
isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, formatInt,
formatTimeStamp, formatTime, parseTimeStamp, splitVersionNumber,
transferCase, fuzzyTime, numberToRoman, jsonEncode, readTextFile,
makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
@@ -68,6 +68,20 @@ def testBaseCommon_CheckInt():
# END Test testBaseCommon_CheckInt
@pytest.mark.base
def testBaseCommon_CheckFloat():
"""Test the checkFloat function.
"""
assert checkFloat(None, 3.0, True) is None
assert checkFloat("None", 3.0, True) is None
assert checkFloat(None, 3.0, False) == 3.0
assert checkFloat(1, 3.0, False) == 1.0
assert checkFloat(1.0, 3.0, False) == 1.0
assert checkFloat(True, 3.0, False) == 1.0
# END Test testBaseCommon_CheckInt
@pytest.mark.base
def testBaseCommon_CheckBool():
"""Test the checkBool function.
+3 -3
View File
@@ -73,7 +73,7 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
assert theOpts.loadSettings()
# Check that unwanted items have been removed
assert theOpts.theState == {
assert theOpts._theState == {
"GuiBuildNovel": {
"winWidth": 1000,
"winHeight": 700,
@@ -88,7 +88,7 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
# Load again to check we get the values back
assert theOpts.loadSettings()
assert theOpts.theState == {
assert theOpts._theState == {
"GuiBuildNovel": {
"winWidth": 1000,
"winHeight": 700,
@@ -129,7 +129,7 @@ def testCoreOptions_SetGet(mockGUI):
assert theOpts.getValue("GuiBuildNovel", "mockItem", None) is None
# Get type-specific
assert theOpts.getString("GuiBuildNovel", "winWidth", None) == "100"
assert theOpts.getString("GuiBuildNovel", "winWidth", None) is None
assert theOpts.getString("GuiBuildNovel", "mockItem", None) is None
assert theOpts.getInt("GuiBuildNovel", "winWidth", None) == 100
assert theOpts.getInt("GuiBuildNovel", "textFont", None) is None
+1 -1
View File
@@ -61,7 +61,7 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir):
assert spChk._readProjectDictionary(None) is False
assert spChk._readProjectDictionary(wList) is True
assert spChk.projectDict == wList
assert spChk._projectDict == wList
# Cannot write to file
with monkeypatch.context() as mp:
+55 -55
View File
@@ -27,59 +27,6 @@ from tools import readFile
from novelwriter.core import NWProject, NWIndex, ToHtml
@pytest.mark.core
def testCoreToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject)
# Export Mode
# ===========
assert theHtml._formatSynopsis("synopsis text") == (
"<p class='synopsis'><strong>Synopsis:</strong> synopsis text</p>\n"
)
assert theHtml._formatComments("comment text") == (
"<p class='comment'><strong>Comment:</strong> comment text</p>\n"
)
assert theHtml._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
)
assert theHtml._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> "
"<a href='#tag_Bod'>Bod</a>, "
"<a href='#tag_Jane'>Jane</a>"
)
# Preview Mode
# ============
theHtml.setPreview(True, True)
assert theHtml._formatSynopsis("synopsis text") == (
"<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n"
)
assert theHtml._formatComments("comment text") == (
"<p class='comment'>comment text</p>\n"
)
assert theHtml._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
)
assert theHtml._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> "
"<a href='#char=Bod'>Bod</a>, "
"<a href='#char=Jane'>Jane</a>"
)
# END Test testCoreToHtml_Format
@pytest.mark.core
def testCoreToHtml_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToHtml class.
@@ -433,7 +380,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
@pytest.mark.core
def testCoreToHtml_Complex(mockGUI, fncDir):
"""Test the ave method of the ToHtml class.
"""Test the save method of the ToHtml class.
"""
theProject = NWProject(mockGUI)
theHtml = ToHtml(theProject)
@@ -524,7 +471,7 @@ def testCoreToHtml_Complex(mockGUI, fncDir):
theHtml.saveHTML5(saveFile)
assert readFile(saveFile) == htmlDoc
# END Test testCoreToHtml_Save
# END Test testCoreToHtml_Complex
@pytest.mark.core
@@ -589,3 +536,56 @@ def testCoreToHtml_Methods(mockGUI):
assert theHtml.getStyleSheet() == []
# END Test testCoreToHtml_Methods
@pytest.mark.core
def testCoreToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject)
# Export Mode
# ===========
assert theHtml._formatSynopsis("synopsis text") == (
"<p class='synopsis'><strong>Synopsis:</strong> synopsis text</p>\n"
)
assert theHtml._formatComments("comment text") == (
"<p class='comment'><strong>Comment:</strong> comment text</p>\n"
)
assert theHtml._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
)
assert theHtml._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> "
"<a href='#tag_Bod'>Bod</a>, "
"<a href='#tag_Jane'>Jane</a>"
)
# Preview Mode
# ============
theHtml.setPreview(True, True)
assert theHtml._formatSynopsis("synopsis text") == (
"<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n"
)
assert theHtml._formatComments("comment text") == (
"<p class='comment'>comment text</p>\n"
)
assert theHtml._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
)
assert theHtml._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> "
"<a href='#char=Bod'>Bod</a>, "
"<a href='#char=Jane'>Jane</a>"
)
# END Test testCoreToHtml_Format
+277
View File
@@ -0,0 +1,277 @@
"""
novelWriter ToMd Class Tester
===============================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from tools import readFile
from novelwriter.core import NWProject, NWIndex, ToMarkdown
@pytest.mark.core
def testCoreToMarkdown_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theMD = ToMarkdown(theProject)
# Headers
# =======
theMD.isNovel = True
theMD.isNote = False
theMD.isFirst = True
# Header 1
theMD.theText = "# Partition\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "# Partition\n\n"
# Header 2
theMD.theText = "## Chapter Title\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "## Chapter Title\n\n"
# Header 3
theMD.theText = "### Scene Title\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "### Scene Title\n\n"
# Header 4
theMD.theText = "#### Section Title\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "#### Section Title\n\n"
# Title
theMD.theText = "#! Title\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "# Title\n\n"
# Unnumbered
theMD.theText = "##! Prologue\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "## Prologue\n\n"
# Paragraphs
# ==========
# Text for GitHub Markdown
theMD.setGitHubMarkdown()
theMD.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == (
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
)
# Text for Standard Markdown
theMD.setStandardMarkdown()
theMD.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == (
"Some **nested bold and _italic_ and strikethrough text** here\n\n"
)
# Text w/Hard Break
theMD.theText = "Line one \nLine two \nLine three\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "Line one \nLine two \nLine three\n\n"
# Synopsis
theMD.theText = "%synopsis: The synopsis ...\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == ""
theMD.setSynopsis(True)
theMD.theText = "%synopsis: The synopsis ...\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "**Synopsis:** The synopsis ...\n\n"
# Comment
theMD.theText = "% A comment ...\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == ""
theMD.setComments(True)
theMD.theText = "% A comment ...\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "**Comment:** A comment ...\n\n"
# Keywords
theMD.theText = "@char: Bod, Jane\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == ""
theMD.setKeywords(True)
theMD.theText = "@char: Bod, Jane\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == "**Characters:** Bod, Jane\n\n"
# Multiple Keywords
theMD.setKeywords(True)
theMD.theText = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == (
"## Chapter\n\n"
"**Point of View:** Bod \n"
"**Plot:** Main \n"
"**Locations:** Europe\n\n"
)
# END Test testCoreToMarkdown_ConvertFormat
@pytest.mark.core
def testCoreToMarkdown_ConvertDirect(mockGUI):
"""Test the converter directly using the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theMD = ToMarkdown(theProject)
theMD.isNovel = True
theMD.isNote = False
# Special Titles
# ==============
# Title
theMD.theTokens = [
(theMD.T_TITLE, 1, "A Title", None, theMD.A_PBB | theMD.A_CENTRE),
(theMD.T_EMPTY, 1, "", None, theMD.A_NONE),
]
theMD.doConvert()
assert theMD.theResult == "# A Title\n\n"
# Unnumbered
theMD.theTokens = [
(theMD.T_UNNUM, 1, "Prologue", None, theMD.A_PBB),
(theMD.T_EMPTY, 1, "", None, theMD.A_NONE),
]
theMD.doConvert()
assert theMD.theResult == "## Prologue\n\n"
# Separators
# ==========
# Separator
theMD.theTokens = [
(theMD.T_SEP, 1, "* * *", None, theMD.A_CENTRE),
(theMD.T_EMPTY, 1, "", None, theMD.A_NONE),
]
theMD.doConvert()
assert theMD.theResult == "* * *\n\n"
# Skip
theMD.theTokens = [
(theMD.T_SKIP, 1, "", None, theMD.A_NONE),
(theMD.T_EMPTY, 1, "", None, theMD.A_NONE),
]
theMD.doConvert()
assert theMD.theResult == "\n\n\n"
# END Test testCoreToMarkdown_ConvertDirect
@pytest.mark.core
def testCoreToMarkdown_Complex(mockGUI, fncDir):
"""Test the save method of the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
theMD = ToMarkdown(theProject)
theMD.isNovel = True
# Build Project
# =============
docText = [
"# My Novel\n**By Jane Doh**\n",
"## Chapter 1\n\nThe text of chapter one.\n",
"### Scene 1\n\nThe text of scene one.\n",
"#### A Section\n\nMore text in scene one.\n",
"## Chapter 2\n\nThe text of chapter two.\n",
"### Scene 2\n\nThe text of scene two.\n",
"#### A Section\n\n\tMore text in scene two.\n",
]
resText = [
"# My Novel\n\n**By Jane Doh**\n\n",
"## Chapter 1\n\nThe text of chapter one.\n\n",
"### Scene 1\n\nThe text of scene one.\n\n",
"#### A Section\n\nMore text in scene one.\n\n",
"## Chapter 2\n\nThe text of chapter two.\n\n",
"### Scene 2\n\nThe text of scene two.\n\n",
"#### A Section\n\n\tMore text in scene two.\n\n",
]
for i in range(len(docText)):
theMD.theText = docText[i]
theMD.doPreProcessing()
theMD.tokenizeText()
theMD.doConvert()
assert theMD.theResult == resText[i]
assert theMD.fullMD == resText
assert theMD.getFullResultSize() == len("".join(resText))
theMD.replaceTabs(nSpaces=4, spaceChar=" ")
resText[6] = "#### A Section\n\n More text in scene two.\n\n"
# Check File
# ==========
saveFile = os.path.join(fncDir, "outFile.md")
theMD.saveMarkdown(saveFile)
assert readFile(saveFile) == "".join(resText)
# END Test testCoreToHtml_Complex
@pytest.mark.core
def testCoreToMarkdown_Format(mockGUI):
"""Test all the formatters for the ToMarkdown class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theMD = ToMarkdown(theProject)
assert theMD._formatKeywords("", theMD.A_NONE) == ""
assert theMD._formatKeywords("tag: Jane", theMD.A_NONE) == "**Tag:** Jane\n\n"
assert theMD._formatKeywords("tag: Jane, John", theMD.A_NONE) == "**Tag:** Jane, John\n\n"
assert theMD._formatKeywords("tag: Jane", theMD.A_Z_BTMMRG) == "**Tag:** Jane \n"
# END Test testCoreToMarkdown_Format
+86
View File
@@ -559,6 +559,62 @@ def testCoreToOdt_Convert(mockGUI):
# END Test testCoreToOdt_Convert
@pytest.mark.core
def testCoreToOdt_ConvertDirect(mockGUI):
"""Test the converter directly using the ToOdt class to reach some
otherwise hard to reach conditions.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
theDoc.isNovel = True
# Justified
theDoc = ToOdt(theProject, isFlat=True)
theDoc.theTokens = [
(theDoc.T_TEXT, 1, "This is a paragraph", [], theDoc.A_JUSTIFY),
(theDoc.T_EMPTY, 1, "", None, theDoc.A_NONE),
]
theDoc.initDocument()
theDoc.doConvert()
theDoc.closeDocument()
assert (
'<style:style style:name="P1" style:family="paragraph" '
'style:parent-style-name="Text_Body">'
'<style:paragraph-properties fo:text-align="justify"/>'
'</style:style>'
) in xmlToText(theDoc._xAuto)
assert xmlToText(theDoc._xText) == (
'<office:text>'
'<text:p text:style-name="P1">This is a paragraph</text:p>'
'</office:text>'
)
# Page Break After
theDoc = ToOdt(theProject, isFlat=True)
theDoc.theTokens = [
(theDoc.T_TEXT, 1, "This is a paragraph", [], theDoc.A_PBA),
(theDoc.T_EMPTY, 1, "", None, theDoc.A_NONE),
]
theDoc.initDocument()
theDoc.doConvert()
theDoc.closeDocument()
assert (
'<style:style style:name="P1" style:family="paragraph" '
'style:parent-style-name="Text_Body">'
'<style:paragraph-properties fo:break-after="page"/>'
'</style:style>'
) in xmlToText(theDoc._xAuto)
assert xmlToText(theDoc._xText) == (
'<office:text>'
'<text:p text:style-name="P1">This is a paragraph</text:p>'
'</office:text>'
)
# END Test testCoreToOdt_ConvertDirect
@pytest.mark.core
def testCoreToOdt_SaveFlat(mockGUI, fncDir, outDir, refDir):
"""Test the document save functions.
@@ -676,6 +732,36 @@ def testCoreToOdt_SaveFull(mockGUI, fncDir, outDir, refDir):
# END Test testCoreToOdt_SaveFull
@pytest.mark.core
def testCoreToOdt_Format(mockGUI):
"""Test the formatters for the ToOdt class.
"""
theProject = NWProject(mockGUI)
mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True)
assert theDoc._formatSynopsis("synopsis text") == (
"**Synopsis:** synopsis text",
"_B b_ "
)
assert theDoc._formatComments("comment text") == (
"**Comment:** comment text",
"_B b_ "
)
assert theDoc._formatKeywords("") == ""
assert theDoc._formatKeywords("tag: Jane") == (
"**Tag:** Jane",
"_B b_ "
)
assert theDoc._formatKeywords("char: Bod, Jane") == (
"**Characters:** Bod, Jane",
"_B b_ "
)
# END Test testCoreToOdt_Format
@pytest.mark.core
def testCoreToOdt_ODTParagraphStyle():
"""Test the ODTParagraphStyle class.
+34 -18
View File
@@ -27,23 +27,22 @@ from PyQt5.QtWidgets import QAction, QMessageBox
from novelwriter.dialogs import GuiAbout
keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgAbout_Dialog(qtbot, monkeypatch, nwGUI):
"""Test the full about dialogs.
def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
"""Test the novelWriter about dialogs.
"""
# NW About
monkeypatch.setattr(GuiAbout, "exec_", lambda *a: None)
nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
# NW About
nwGUI.theTheme.themeName = "A Theme"
nwGUI.theTheme.themeAuthor = "An Author"
assert nwGUI.showAboutNWDialog(showNotes=True) is True
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
msgAbout = getGuiItem("GuiAbout")
assert isinstance(msgAbout, GuiAbout)
msgAbout.show()
assert msgAbout.pageAbout.document().characterCount() > 100
assert msgAbout.pageNotes.document().characterCount() > 100
@@ -59,12 +58,29 @@ def testDlgAbout_Dialog(qtbot, monkeypatch, nwGUI):
msgAbout.showReleaseNotes()
assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
# Qt About
monkeypatch.setattr(QMessageBox, "aboutQt", lambda *a, **k: None)
nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
# qtbot.stopForInteraction()
msgAbout._doClose()
# END Test testDlgAbout_Dialog
# Open Again from Menu
nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
msgAbout = getGuiItem("GuiAbout")
assert msgAbout is not None
msgAbout._doClose()
# END Test testDlgAbout_NWDialog
@pytest.mark.gui
def testDlgAbout_QtDialog(monkeypatch, nwGUI):
"""Test the Qt about dialogs.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "aboutQt", lambda *a, **k: None)
# Open About
# All it can do is check aigainst a crash
assert nwGUI.showAboutQtDialog() is True
nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
# END Test testDlgAbout_QtDialog
@@ -31,10 +31,6 @@ from novelwriter.dialogs import GuiDocMerge, GuiItemEditor
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.core.tree import NWTree
keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj):
@@ -96,7 +92,7 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj):
nwMerge = getGuiItem("GuiDocMerge")
assert isinstance(nwMerge, GuiDocMerge)
nwMerge.show()
qtbot.wait(stepDelay)
qtbot.wait(50)
# Populate List
# =============
@@ -32,10 +32,6 @@ from novelwriter.enum import nwItemType, nwWidget
from novelwriter.core.document import NWDoc
from novelwriter.core.tree import NWTree
keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
@@ -103,7 +99,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
nwSplit = getGuiItem("GuiDocSplit")
assert isinstance(nwSplit, GuiDocSplit)
nwSplit.show()
qtbot.wait(stepDelay)
qtbot.wait(50)
# Populate List
# =============
@@ -253,7 +249,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
nwSplit.sourceItem = None
assert nwSplit._doSplit() is False
# Close up
# Close
nwSplit._doClose()
# qtbot.stopForInteraction()
+175 -55
View File
@@ -20,93 +20,213 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
from shutil import copyfile
from tools import cmpFiles, getGuiItem
from tools import getGuiItem
from PyQt5.QtWidgets import QAction, QMessageBox
from PyQt5.QtWidgets import QAction, QDialog, QMessageBox
from novelwriter.gui import GuiProjectTree
from novelwriter.enum import nwItemLayout
from novelwriter.enum import nwItemLayout, nwItemType
from novelwriter.dialogs import GuiItemEditor
keyDelay = 2
typeDelay = 1
stepDelay = 20
from novelwriter.core.tree import NWTree
@pytest.mark.gui
def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
"""Test the full item editor dialog.
def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj):
"""Test launching the item editor dialog from GuiMain.
"""
projFile = os.path.join(fncProj, "nwProject.nwx")
testFile = os.path.join(outDir, "guiItemEditor_Dialog_nwProject.nwx")
compFile = os.path.join(refDir, "guiItemEditor_Dialog_nwProject.nwx")
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
# Block Dialog exec_
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
# Open Editor wo/Project
assert nwGUI.editItem() is False
# Create and Open Project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
# No Selection
nwGUI.treeView.clearSelection()
assert nwGUI.editItem() is False
# Force opening from editor
assert nwGUI.openDocument("0e17daca5f3e1")
nwGUI.isFocusMode = True
# Block Tree Lookup
with monkeypatch.context() as mp:
mp.setattr(NWTree, "__getitem__", lambda *a: None)
assert nwGUI.editItem() is False
# Invalid Type
nwGUI.theProject.projTree["0e17daca5f3e1"].itemType = nwItemType.NO_TYPE
assert nwGUI.editItem() is False
nwGUI.theProject.projTree["0e17daca5f3e1"].itemType = nwItemType.FILE
# Open Properly
assert nwGUI.editItem() is True
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
itemEdit = getGuiItem("GuiItemEditor")
assert itemEdit is not None
itemEdit.close()
# Open Via Menu
with monkeypatch.context() as mp:
mp.setattr(GuiItemEditor, "result", lambda *a: QDialog.Accepted)
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
itemEdit = getGuiItem("GuiItemEditor")
assert itemEdit is not None
itemEdit.close()
nwGUI.isFocusMode = False
# END Test testDlgItemEditor_Dialog
@pytest.mark.gui
def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the item editor dialog for a novel document.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create new, save, open project
# Create Project and Open Document
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
assert nwGUI.openDocument("0e17daca5f3e1")
assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True)
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
# Check that an invalid handle is managed
itemEdit = GuiItemEditor(nwGUI, "whatever")
itemEdit.show()
itemEdit._doClose()
itemEdit = getGuiItem("GuiItemEditor")
assert isinstance(itemEdit, GuiItemEditor)
# Edit a Document
itemEdit = GuiItemEditor(nwGUI, "0e17daca5f3e1")
itemEdit.show()
qtbot.addWidget(itemEdit)
# Check Existing Settings
assert itemEdit.editName.text() == "New Scene"
assert itemEdit.editStatus.currentData() == "New"
assert itemEdit.editLayout.currentData() == nwItemLayout.DOCUMENT
assert itemEdit.editExport.isChecked() is True
for c in "Just a Page":
qtbot.keyClick(itemEdit.editName, c, delay=typeDelay)
# Change Settings
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.NOTE)
itemEdit.editName.setText("Great Scene")
itemEdit.editStatus.setCurrentIndex(1)
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.DOCUMENT)
itemEdit.editLayout.setCurrentIndex(layoutIdx)
itemEdit.editExport.setChecked(False)
assert not itemEdit.editExport.isChecked()
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "Great Scene"
assert itemEdit.theItem.itemStatus == "Note"
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
itemEdit = getGuiItem("GuiItemEditor")
assert isinstance(itemEdit, GuiItemEditor)
itemEdit.show()
qtbot.addWidget(itemEdit)
assert itemEdit.editName.text() == "Just a Page"
assert itemEdit.editStatus.currentData() == "Note"
assert itemEdit.editLayout.currentData() == nwItemLayout.DOCUMENT
itemEdit._doClose()
# Check that the header is updated
# Check that the editor header is updated
nwGUI.docEditor.updateDocInfo("0e17daca5f3e1")
assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel New Chapter Just a Page"
assert not nwGUI.docEditor.setCursorLine("where?")
assert nwGUI.docEditor.setCursorLine(2)
qtbot.wait(stepDelay)
assert nwGUI.docEditor.getCursorPosition() == 15
qtbot.wait(stepDelay)
assert nwGUI.saveProject()
qtbot.wait(stepDelay)
# Check the files
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel New Chapter Great Scene"
itemEdit.close()
del itemEdit
# qtbot.stopForInteraction()
# END Test testDlgItemEditor_Dialog
@pytest.mark.gui
def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the item editor dialog for a project note.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
# Create Note
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
# Open Note
assert nwGUI.openDocument("1a6562590ef19")
# Edit a Document
itemEdit = GuiItemEditor(nwGUI, "1a6562590ef19")
itemEdit.show()
# Check Existing Settings
assert itemEdit.editName.text() == "New File"
assert itemEdit.editStatus.currentData() == "New"
assert itemEdit.editLayout.currentData() == nwItemLayout.NOTE
assert itemEdit.editExport.isChecked() is True
# Change Settings
itemEdit.editName.setText("New Character")
itemEdit.editStatus.setCurrentIndex(1)
itemEdit.editExport.setChecked(False)
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "New Character"
assert itemEdit.theItem.itemStatus == "Minor"
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False
itemEdit.close()
del itemEdit
# qtbot.stopForInteraction()
# END Test testDlgItemEditor_Note
@pytest.mark.gui
def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the item editor dialog for a folder.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
# Edit a Folder
itemEdit = GuiItemEditor(nwGUI, "31489056e0916")
itemEdit.show()
# Check Existing Settings
assert itemEdit.editName.text() == "New Chapter"
assert itemEdit.editStatus.currentData() == "New"
assert itemEdit.editLayout.currentData() == nwItemLayout.NO_LAYOUT
assert itemEdit.editExport.isChecked() is False
assert itemEdit.editLayout.isEnabled() is False
assert itemEdit.editExport.isEnabled() is False
# Change Settings
itemEdit.editName.setText("Chapter One")
itemEdit.editStatus.setCurrentIndex(1)
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "Chapter One"
assert itemEdit.theItem.itemStatus == "Note"
assert itemEdit.theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert itemEdit.theItem.isExported is False
itemEdit.close()
del itemEdit
# qtbot.stopForInteraction()
# END Test testDlgItemEditor_Folder