Merge branch 'main' into release_1.0b1

This commit is contained in:
Veronica K. B. Olsen
2020-08-28 16:32:28 +02:00
35 changed files with 925 additions and 397 deletions
+9 -8
View File
@@ -57,26 +57,27 @@ already familiar with how to run Python applications on your platform.
:maxdepth: 2
:caption: First Steps
introduction
started
interface
int_introduction
int_started
int_interface
int_typography
.. toctree::
:maxdepth: 2
:caption: Writing Novels
projects
structure
notes
export
write_projects
write_structure
write_notes
write_export
.. toctree::
:maxdepth: 2
:caption: Under the Hood
technical
tech_technical
Indices and Tables
@@ -1,8 +1,8 @@
.. _a_ui:
***************
**************
User Interface
***************
**************
The user interface is kept as simple as possible to avoid distractions when writing. This page lists
all the main GUI elements, and explains what they do.
@@ -341,11 +341,12 @@ Most features are available as keyboard shortcuts. These are as follows:
":kbd:`Ctrl`:kbd:`B`", "Format selected text, or word under cursor, with strong emphasis (bold)."
":kbd:`Ctrl`:kbd:`C`", "Copy selected text to clipboard."
":kbd:`Ctrl`:kbd:`D`", "Wrap selected text, or word under cursor, in double quotes."
":kbd:`Ctrl`:kbd:`E`", "If in the project tree, edit a document or folder settings. (Same as :kbd:`F2`)"
":kbd:`Ctrl`:kbd:`E`", "If in the project tree, edit a document or folder settings. (Same as :kbd:`F2`.)"
":kbd:`Ctrl`:kbd:`F`", "Open the search bar and search for the selected word, if any is selected."
":kbd:`Ctrl`:kbd:`G`", "Find next occurrence of search word in current document. (Same as :kbd:`F3`)"
":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`)"
":kbd:`Ctrl`:kbd:`G`", "Find next occurrence of search word in current document. (Same as :kbd:`F3`.)"
":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`.)"
":kbd:`Ctrl`:kbd:`I`", "Format selected text, or word under cursor, with emphasis (italic)."
":kbd:`Ctrl`:kbd:`K`", "Activate the insert commands. The commands are listed in :ref:`a_ui_shortcuts_ins`."
":kbd:`Ctrl`:kbd:`N`", "Create new document."
":kbd:`Ctrl`:kbd:`O`", "Open selected document."
":kbd:`Ctrl`:kbd:`Q`", "Exit novelWriter."
@@ -365,7 +366,7 @@ Most features are available as keyboard shortcuts. These are as follows:
":kbd:`Ctrl`:kbd:`Shift`:kbd:`1`", "Replace occurrence of search word in current document, and search for next occurrence."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`A`", "Select all text in current paragraph."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`D`", "Wrap selected text, or word under cursor, in single quotes."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document. (Same as :kbd:`Shift`:kbd:`F3`)"
":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document. (Same as :kbd:`Shift`:kbd:`F3`.)"
":kbd:`Ctrl`:kbd:`Shift`:kbd:`I`", "Import text to the current document from a text file."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`N`", "Create new folder."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`O`", "Open a project."
@@ -386,7 +387,7 @@ Most features are available as keyboard shortcuts. These are as follows:
":kbd:`F10`", "Re-build the project outline."
":kbd:`F11`", "Activate full screen mode."
":kbd:`Shift`:kbd:`F1`", "Open the online documentation in the system default browser."
":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document. (Same as :kbd:`Ctrl`:kbd:`Shift`:kbd:`G`)"
":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document. (Same as :kbd:`Ctrl`:kbd:`Shift`:kbd:`G`.)"
":kbd:`Return`", "If in the project tree, open a document for editing."
.. note::
@@ -409,11 +410,12 @@ combination for the inserted character or punctuation.
":kbd:`Ctrl`:kbd:`K`, :kbd:`-`", "Insert a short dash (en dash)."
":kbd:`Ctrl`:kbd:`K`, :kbd:`_`", "Insert a long dash (em dash)."
":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert ellipsis."
":kbd:`Ctrl`:kbd:`K`, :kbd:`1`", "Insert left single quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`2`", "Insert right single quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`3`", "Insert left double quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`4`", "Insert right double quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert an ellipsis."
":kbd:`Ctrl`:kbd:`K`, :kbd:`1`", "Insert a left single quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`2`", "Insert a right single quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`3`", "Insert a left double quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`4`", "Insert a right double quote."
":kbd:`Ctrl`:kbd:`K`, :kbd:`'`", "Insert a modifier apostrophe."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Return`", "Insert a hard line break."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Space`", "Insert a non-breaking space."
":kbd:`Ctrl`:kbd:`K`, :kbd:`Shift`:kbd:`Space`", "Insert a thin space."
+87
View File
@@ -0,0 +1,87 @@
.. _a_typ:
*******************
Typographical Notes
*******************
novelWriter has some support for typographical symbols that are not usually easily available in many
text editors. This includes for instance the proper unicode quotation marks, dashes, ellipsis, thin
spaces, etc. All these symbols are available from the :guilabel:`Insert` menu, and via keyboard
shortcuts. See :ref:`a_ui_shortcuts_ins`.
This chapter provides some additional information on how novelWriter handles these symbols.
.. _a_typ_notes:
Special Notes on Symbols
========================
Some additional notes on these symbols.
Dashes and Ellipsis
-------------------
With the auto-replace feature enabled (see :ref:`a_ui_edit_auto`), multiple hyphens are converted
automatically to short and long dashes, and three dots to ellipsis. The last auto-replace can always
be reverted with the undo command :kbd:`Ctrl`:kbd:`Z`, reverting the text to what you typed before
the automatic replacement occurred.
Single and Double Quotes
------------------------
All the different quotation marks listed on the `Quotation Mark`_ Wikipedia page are available, and
can be selected as auto-replaced symbols for straight single and double quote key strokes. The
settings can be found in the :guilabel:`Preferences`.
Ordinarily, text wrapped in quotes are highlighted by the editor. This is meant as a convenience for
highlighting dialogue between characters. This feature can be disabled in the
:guilabel:`Preferences` if this feature isn't wanted.
The editor distinguishes between text wrapped in straight quotes and with the user-selected double
quote symbols. This is to help the writer recognise which parts of the text are not using the chosen
quote symbols. Two convenience functions in the :guilabel:`Format` menu can be used to re-format a
selected section of text with the correct quote symbols.
.. _Quotation Mark: https://en.wikipedia.org/wiki/Quotation_mark
Modifier Letter Apostrophes
---------------------------
The auto-replace feature will consider any right-facing single straight quote as a quote symbol,
even if it's intended as an apostrophe. This also includes the syntax highlighter, which may assume
the first following apostrophe is the closing symbol of a single quoted region of text.
To get around this, an alternative apostrophe is available. It is a special Unicode character that
is not categorised as punctuation, but as a modifier. It is usually renderred the same way as right
single quotation marks, but not always, depending on the font. There is a Wikipedia article for the
`Modifier letter apostrophe`_ with more details.
.. note::
On export with the :guilabel:`Build Novel Project` tool, these apostrophes will be replaced
automatically with the corresponding right hand quote symbols as is generally recommended.
Therefore it doesn't really matter if you only use them to correct highlighting.
.. _Modifier letter apostrophe: https://en.wikipedia.org/wiki/Modifier_letter_apostrophe
Special Space Symbols
---------------------
A few variations of the regular space character is supported. The correct typographical way to
separate a number from its unit is with a `thin space`_. It is usually 2/3 the width of a regular
space. For numbers and units, this should in addition be a non-breaking space, that is, the text
wrapping should not add a line break on this particular space.
A regular space can also be made into a non-breaking space.
All non-breaking spaces are highlighted with a differently coloured packground. The colour will
depend on the selected colour theme.
The thin and non-breaking spaces are converted to their corresponding HTML codes on export to HTML
format. For plain text, they are exported as regular spaces.
.. _thin space: https://en.wikipedia.org/wiki/Thin_space
+2
View File
@@ -233,6 +233,7 @@ class nwUnicode:
U_ENDASH = "\u2013" # Short dash
U_EMDASH = "\u2014" # Long dash
U_HELLIP = "\u2026" # Ellipsis
U_MAPOSS = "\u02bc" # Modifier letter single apostrophe
## Spaces and Lines
U_NBSP = "\u00a0" # Non-breaking space
@@ -283,6 +284,7 @@ class nwUnicode:
H_ENDASH = "–"
H_EMDASH = "—"
H_HELLIP = "…"
H_MAPOSS = "ʼ"
## Spaces
H_NBSP = " "
+1
View File
@@ -112,6 +112,7 @@ class nwDocInsert(Enum):
QUOTE_RS = 9
QUOTE_LD = 10
QUOTE_RD = 11
MODAPOS_S = 12
# END Enum nwDocInsert
+1
View File
@@ -54,6 +54,7 @@ class ToHtml(Tokenizer):
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
nwUnicode.U_THNSP : nwUnicode.H_THNSP,
nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
nwUnicode.U_MAPOSS : nwUnicode.H_RSQUO,
}
self.revDict = {}
self.reReplace = []
+2
View File
@@ -612,6 +612,8 @@ class GuiDocEditor(QTextEdit):
theText = nwUnicode.U_EMDASH
elif theInsert == nwDocInsert.ELLIPSIS:
theText = nwUnicode.U_HELLIP
elif theInsert == nwDocInsert.MODAPOS_S:
theText = nwUnicode.U_MAPOSS
elif theInsert == nwDocInsert.QUOTE_LS:
theText = self.typSQOpen
elif theInsert == nwDocInsert.QUOTE_RS:
+4 -4
View File
@@ -143,17 +143,17 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Quoted Strings
if self.mainConf.highlightQuotes:
self.hRules.append((
"{:s}(.+?){:s}".format('"', '"'), {
"\\B{:s}(.*?){:s}\\B".format('"', '"'), {
0 : self.hStyles["dialogue1"],
}
))
self.hRules.append((
"{:s}(.+?){:s}".format(*self.mainConf.fmtDoubleQuotes), {
"\\B{:s}(.*?){:s}\\B".format(*self.mainConf.fmtDoubleQuotes), {
0 : self.hStyles["dialogue2"],
}
))
self.hRules.append((
"{:s}(.+?){:s}".format(*self.mainConf.fmtSingleQuotes), {
"\\B{:s}(.*?){:s}\\B".format(*self.mainConf.fmtSingleQuotes), {
0 : self.hStyles["dialogue3"],
}
))
@@ -198,7 +198,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Build a QRegExp for spell checker
# Include additional characters that the highlighter should
# consider to be word separators
wordSep = r"_\+"
wordSep = r"_\+/"
wordSep += nwUnicode.U_ENDASH
wordSep += nwUnicode.U_EMDASH
self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b")
+10
View File
@@ -554,6 +554,16 @@ class GuiMainMenu(QMenuBar):
# Insert > Separator
self.insertMenu.addSeparator()
# Insert > Alternative Apostrophe
self.aInsMSApos = QAction("Alternative Apostrophe", self)
self.aInsMSApos.setStatusTip("Insert unicode modifier letter single apostrophe")
self.aInsMSApos.setShortcut("Ctrl+K, '")
self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_S))
self.insertMenu.addAction(self.aInsMSApos)
# Insert > Separator
self.insertMenu.addSeparator()
# Insert > Hard Line Break
self.aInsHardBreak = QAction("Hard Line Break", self)
self.aInsHardBreak.setStatusTip("Insert a hard line break")
+1
View File
@@ -1039,6 +1039,7 @@ class GuiMain(QMainWindow):
self.addAction(self.mainMenu.aInsQuoteRS)
self.addAction(self.mainMenu.aInsQuoteLD)
self.addAction(self.mainMenu.aInsQuoteRD)
self.addAction(self.mainMenu.aInsMSApos)
self.addAction(self.mainMenu.aInsHardBreak)
self.addAction(self.mainMenu.aInsNBSpace)
self.addAction(self.mainMenu.aInsThinSpace)
+53 -23
View File
@@ -5,10 +5,14 @@
import sys
import pytest
import shutil
from os import path, mkdir
from nwdummy import DummyMain
sys.path.insert(1, path.abspath(path.join(path.dirname(__file__), path.pardir)))
from nw.config import Config # noqa: E402
@pytest.fixture(scope="session")
def nwTemp():
testDir = path.dirname(__file__)
@@ -19,6 +23,31 @@ def nwTemp():
mkdir(tempDir)
return tempDir
@pytest.fixture(scope="session")
def nwRef():
testDir = path.dirname(__file__)
refDir = path.join(testDir, "reference")
return refDir
@pytest.fixture(scope="session")
def nwConf(nwRef, nwTemp):
theConf = Config()
theConf.initConfig(nwRef, nwTemp)
return theConf
@pytest.fixture(scope="session")
def tmpConf(nwRef, nwTemp):
theConf = Config()
theConf.initConfig(nwTemp, nwTemp)
theConf.setLastPath("")
return theConf
@pytest.fixture(scope="session")
def nwDummy(nwRef, nwTemp, nwConf):
theDummy = DummyMain()
theDummy.mainConf = nwConf
return theDummy
@pytest.fixture(scope="session")
def nwTempProj(nwTemp):
projDir = path.join(nwTemp, "proj")
@@ -40,20 +69,6 @@ def nwTempBuild(nwTemp):
mkdir(buildDir)
return buildDir
@pytest.fixture(scope="session")
def nwTempCustom(nwTemp):
customDir = path.join(nwTemp, "custom")
if not path.isdir(customDir):
mkdir(customDir)
return customDir
@pytest.fixture(scope="session")
def nwTempSample(nwTemp):
sampleDir = path.join(nwTemp, "sample")
if not path.isdir(sampleDir):
mkdir(sampleDir)
return sampleDir
@pytest.fixture(scope="function")
def nwFuncTemp(nwTemp):
funcDir = path.join(nwTemp, "ftemp")
@@ -66,18 +81,30 @@ def nwFuncTemp(nwTemp):
shutil.rmtree(funcDir)
return
@pytest.fixture(scope="session")
def nwRef():
@pytest.fixture(scope="function")
def nwMinimal(nwTemp):
testDir = path.dirname(__file__)
refDir = path.join(testDir, "reference")
return refDir
minimalStore = path.join(testDir, "minimal")
minimalDir = path.join(nwTemp, "minimal")
if path.isdir(minimalDir):
shutil.rmtree(minimalDir)
shutil.copytree(minimalStore, minimalDir)
cacheDir = path.join(minimalDir, "cache")
if path.isdir(cacheDir):
shutil.rmtree(cacheDir)
metaDir = path.join(minimalDir, "meta")
if path.isdir(metaDir):
shutil.rmtree(metaDir)
yield minimalDir
if path.isdir(minimalDir):
shutil.rmtree(minimalDir)
return
@pytest.fixture(scope="session")
def nwLipsum():
@pytest.fixture(scope="function")
def nwLipsum(nwTemp):
testDir = path.dirname(__file__)
tempDir = path.join(testDir, "temp")
lipsumStore = path.join(testDir, "lipsum")
lipsumDir = path.join(tempDir, "lipsum")
lipsumDir = path.join(nwTemp, "lipsum")
if path.isdir(lipsumDir):
shutil.rmtree(lipsumDir)
shutil.copytree(lipsumStore, lipsumDir)
@@ -87,4 +114,7 @@ def nwLipsum():
metaDir = path.join(lipsumDir, "meta")
if path.isdir(metaDir):
shutil.rmtree(metaDir)
return lipsumDir
yield lipsumDir
if path.isdir(lipsumDir):
shutil.rmtree(lipsumDir)
return
+1 -1
View File
@@ -27,7 +27,7 @@
[
"content/7a992350f3eb6.nwd",
"NOVEL",
"Lorem Ipusm"
"Lorem Ipsum"
],
[
"content/846352075de7d.nwd",
+1 -1
View File
@@ -9,7 +9,7 @@
content/441420a886d82.nwd NOVEL Chapter Two
content/47666c91c7ccf.nwd NOVEL Scene Five
content/4c4f28287af27.nwd CHARACTER Mr. Nobody
content/7a992350f3eb6.nwd NOVEL Lorem Ipusm
content/7a992350f3eb6.nwd NOVEL Lorem Ipsum
content/846352075de7d.nwd NOVEL Interlude
content/88243afbe5ed8.nwd NOVEL Scene One
content/88d59a277361b.nwd NOVEL Prologue
+8 -6
View File
@@ -1,23 +1,25 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-13 00:37:49">
<novelWriterXML appVersion="0.12.1" hexVersion="0x001201f0" fileVersion="1.2" timeStamp="2020-08-28 15:49:43">
<project>
<name>Lorem Ipsum</name>
<title>Lorem Ipsum</title>
<author>lipsum.com</author>
<saveCount>7</saveCount>
<saveCount>8</saveCount>
<autoCount>21</autoCount>
<editTime>1459</editTime>
<editTime>1472</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>04468803b92e1</lastEdited>
<lastEdited>fb609cd8319dc</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>3847</lastWordCount>
<novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount>
<autoReplace>
<Rep1>Replace Text 1</Rep1>
<Rep2>Replace Text 2</Rep2>
<entry key="Rep1">Replace Text 1</entry>
<entry key="Rep2">Replace Text 2</entry>
</autoReplace>
<titleFormat>
<title>%title%</title>
+17
View File
@@ -0,0 +1,17 @@
[
[
"content/8c659a11cd429.nwd",
"NOVEL",
"New Scene"
],
[
"content/a35baf2e93843.nwd",
"NOVEL",
"Title Page"
],
[
"content/f5ab3e30151e1.nwd",
"NOVEL",
"New Chapter"
]
]
+10
View File
@@ -0,0 +1,10 @@
Table of Contents
===================
File Name Class Document Label
--------------------------------------------------------------------------------
content/8c659a11cd429.nwd NOVEL New Scene
content/a35baf2e93843.nwd NOVEL Title Page
content/f5ab3e30151e1.nwd NOVEL New Chapter
+3
View File
@@ -0,0 +1,3 @@
%%~ 8c659a11cd429:a6d311a93600a:a508bb932959c:NOVEL:SCENE:New Scene
### New Scene
+4
View File
@@ -0,0 +1,4 @@
%%~ a35baf2e93843:a508bb932959c:NOVEL:TITLE:Title Page
# Minimal
By Jane Doe, John Doh
+3
View File
@@ -0,0 +1,3 @@
%%~ f5ab3e30151e1:a6d311a93600a:a508bb932959c:NOVEL:CHAPTER:New Chapter
## New Chapter
+1
View File
@@ -0,0 +1 @@
{}
+2
View File
@@ -0,0 +1,2 @@
# Start Time End Time Novel Notes
2020-08-28 11:36:36 2020-08-28 11:36:48 10 0
+91
View File
@@ -0,0 +1,91 @@
{
"tagIndex": {},
"refIndex": {
"a35baf2e93843": {
"T000000": {
"tags": [],
"updated": 1598607396
},
"T000001": {
"tags": [],
"updated": 1598607396
}
},
"f5ab3e30151e1": {
"T000000": {
"tags": [],
"updated": 1598607396
},
"T000001": {
"tags": [],
"updated": 1598607396
}
},
"8c659a11cd429": {
"T000000": {
"tags": [],
"updated": 1598607396
},
"T000001": {
"tags": [],
"updated": 1598607396
}
}
},
"novelIndex": {
"a35baf2e93843": {
"T000001": {
"level": "H1",
"title": "Minimal",
"layout": "TITLE",
"synopsis": "",
"cCount": 7,
"wCount": 1,
"pCount": 0,
"updated": 1598607396
}
},
"f5ab3e30151e1": {
"T000001": {
"level": "H2",
"title": "New Chapter",
"layout": "CHAPTER",
"synopsis": "",
"cCount": 11,
"wCount": 2,
"pCount": 0,
"updated": 1598607396
}
},
"8c659a11cd429": {
"T000001": {
"level": "H3",
"title": "New Scene",
"layout": "SCENE",
"synopsis": "",
"cCount": 9,
"wCount": 2,
"pCount": 0,
"updated": 1598607396
}
}
},
"noteIndex": {},
"textCounts": {
"a35baf2e93843": [
28,
6,
1
],
"f5ab3e30151e1": [
11,
2,
0
],
"8c659a11cd429": [
9,
2,
0
]
}
}
+115
View File
@@ -0,0 +1,115 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.12.1" hexVersion="0x001201f0" fileVersion="1.2" timeStamp="2020-08-28 11:36:45">
<project>
<name>Test Minimal</name>
<title>Minimal</title>
<author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>8</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>10</lastWordCount>
<novelWordCount>10</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %ch%: %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="a508bb932959c" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="a35baf2e93843" order="0" parent="a508bb932959c">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>28</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="a6d311a93600a" order="1" parent="a508bb932959c">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="f5ab3e30151e1" order="0" parent="a6d311a93600a">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="8c659a11cd429" order="1" parent="a6d311a93600a">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="7695ce551d265" order="1" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="afb3043c7b2b3" order="2" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="9d5247ab588e0" order="3" parent="None">
<name>World</name>
<type>ROOT</type>
<class>WORLD</class>
<status>New</status>
<expanded>False</expanded>
</item>
</content>
</novelWriterXML>
+5 -33
View File
@@ -1,9 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 21:29:43">
<novelWriterXML appVersion="0.12.1" hexVersion="0x001201f0" fileVersion="1.2" timeStamp="2020-08-28 11:58:47">
<project>
<name>New Project</name>
<title></title>
<saveCount>5</saveCount>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="14">
<content count="10">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -109,35 +109,7 @@
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="1a6562590ef19" order="None" parent="None">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="031b4af5197ec" order="None" parent="None">
<name>Object</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="41cfc0d1f2d12" order="None" parent="None">
<name>Custom1</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="2858dcd1057d3" order="None" parent="None">
<name>Custom2</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="2fca346db6561" order="None" parent="73475cb40a568">
<item handle="1a6562590ef19" order="None" parent="31489056e0916">
<name>Hello</name>
<type>FILE</type>
<class>NOVEL</class>
@@ -149,7 +121,7 @@
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="02d20bbd7e394" order="None" parent="71ee45a3c0db9">
<item handle="031b4af5197ec" order="None" parent="71ee45a3c0db9">
<name>Jane</name>
<type>FILE</type>
<class>CHARACTER</class>
+87 -89
View File
@@ -5,112 +5,110 @@
import pytest
from nwtools import cmpFiles
from os import path
from nw.config import Config
theConf = Config()
@pytest.mark.core
def testConfigInit(nwTemp, nwRef):
tmpConf = path.join(nwTemp, "novelwriter.conf")
def testConfigCore(tmpConf, nwTemp, nwRef):
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.initConfig(nwTemp, nwTemp)
assert theConf.setLastPath("")
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
assert tmpConf.saveConfig()
assert cmpFiles(testConf, refConf, [2])
assert not tmpConf.confChanged
assert tmpConf.loadConfig()
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigSave(nwTemp, nwRef):
tmpConf = path.join(nwTemp, "novelwriter.conf")
def testConfigSetConfPath(tmpConf, nwTemp):
assert tmpConf.setConfPath(None)
assert not tmpConf.setConfPath(path.join("somewhere", "over", "the", "rainbow"))
assert tmpConf.setConfPath(path.join(nwTemp, "novelwriter.conf"))
assert tmpConf.confPath == nwTemp
assert tmpConf.confFile == "novelwriter.conf"
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigSetDataPath(tmpConf, nwTemp):
assert tmpConf.setDataPath(None)
assert not tmpConf.setDataPath(path.join("somewhere", "over", "the", "rainbow"))
assert tmpConf.setDataPath(nwTemp)
assert tmpConf.dataPath == nwTemp
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigSetWinSize(tmpConf, nwTemp, nwRef):
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.confPath == nwTemp
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
assert tmpConf.setWinSize(1105, 655)
assert not tmpConf.confChanged
assert tmpConf.setWinSize(70, 70)
assert tmpConf.confChanged
assert tmpConf.setWinSize(1100, 650)
assert tmpConf.saveConfig()
assert cmpFiles(testConf, refConf, [2])
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigSetConfPath(nwTemp):
assert theConf.setConfPath(None)
assert not theConf.setConfPath(path.join("somewhere", "over", "the", "rainbow"))
assert theConf.setConfPath(path.join(nwTemp, "novelwriter.conf"))
assert theConf.confPath == nwTemp
assert theConf.confFile == "novelwriter.conf"
assert not theConf.confChanged
@pytest.mark.core
def testConfigSetDataPath(nwTemp):
assert theConf.setDataPath(None)
assert not theConf.setDataPath(path.join("somewhere", "over", "the", "rainbow"))
assert theConf.setDataPath(nwTemp)
assert theConf.dataPath == nwTemp
assert not theConf.confChanged
@pytest.mark.core
def testConfigLoad():
assert theConf.loadConfig()
assert not theConf.confChanged
@pytest.mark.core
def testConfigSetWinSize(nwTemp, nwRef):
tmpConf = path.join(nwTemp, "novelwriter.conf")
def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef):
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.setWinSize(1105, 655)
assert not theConf.confChanged
assert theConf.setWinSize(70, 70)
assert theConf.confChanged
assert theConf.setWinSize(1100, 650)
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
assert tmpConf.setTreeColWidths([0, 0, 0])
assert tmpConf.confChanged
assert tmpConf.setTreeColWidths([120, 30, 50])
assert tmpConf.setProjColWidths([140, 55, 140])
assert tmpConf.saveConfig()
assert cmpFiles(testConf, refConf, [2])
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigSetTreeColWidths(nwTemp, nwRef):
tmpConf = path.join(nwTemp, "novelwriter.conf")
def testConfigSetPanePos(tmpConf, nwTemp, nwRef):
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.setTreeColWidths([0, 0, 0])
assert theConf.confChanged
assert theConf.setTreeColWidths([120, 30, 50])
assert theConf.setProjColWidths([140, 55, 140])
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
assert tmpConf.setMainPanePos([0, 0])
assert tmpConf.confChanged
assert tmpConf.setMainPanePos([300, 800])
assert tmpConf.setDocPanePos([400, 400])
assert tmpConf.setViewPanePos([500, 150])
assert tmpConf.setOutlinePanePos([500, 150])
assert tmpConf.saveConfig()
assert cmpFiles(testConf, refConf, [2])
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigSetPanePos(nwTemp, nwRef):
tmpConf = path.join(nwTemp, "novelwriter.conf")
def testConfigFlags(tmpConf, nwTemp, nwRef):
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.setMainPanePos([0, 0])
assert theConf.confChanged
assert theConf.setMainPanePos([300, 800])
assert theConf.setDocPanePos([400, 400])
assert theConf.setViewPanePos([500, 150])
assert theConf.setOutlinePanePos([500, 150])
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
assert not tmpConf.setShowRefPanel(False)
assert tmpConf.setShowRefPanel(True)
assert tmpConf.confChanged
assert tmpConf.saveConfig()
assert cmpFiles(testConf, refConf, [2])
assert not tmpConf.confChanged
@pytest.mark.core
def testConfigFlags(nwTemp, nwRef):
tmpConf = path.join(nwTemp, "novelwriter.conf")
refConf = path.join(nwRef, "novelwriter.conf")
assert not theConf.setShowRefPanel(False)
assert theConf.setShowRefPanel(True)
assert theConf.confChanged
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
@pytest.mark.core
def testConfigErrors(nwTemp):
def testConfigErrors(tmpConf):
nonPath = path.join("somewhere", "over", "the", "rainbow")
assert theConf.initConfig(nonPath, nonPath)
assert theConf.hasError
assert not theConf.loadConfig()
assert not theConf.saveConfig()
assert not theConf.loadRecentCache()
assert len(theConf.getErrData()) > 0
assert tmpConf.initConfig(nonPath, nonPath)
assert tmpConf.hasError
assert not tmpConf.loadConfig()
assert not tmpConf.saveConfig()
assert not tmpConf.loadRecentCache()
assert len(tmpConf.getErrData()) > 0
@pytest.mark.core
def testConfigInternals():
assert theConf._checkNone(None) is None
assert theConf._checkNone("None") is None
def testConfigInternals(tmpConf):
assert tmpConf._checkNone(None) is None
assert tmpConf._checkNone("None") is None
+245 -123
View File
@@ -5,6 +5,7 @@
import nw
import pytest
import json
from shutil import copyfile
from nwtools import cmpFiles
from os import path
@@ -24,8 +25,9 @@ keyDelay = 2
stepDelay = 20
@pytest.mark.gui
def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testMainWindows(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -33,7 +35,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
# Create new, save, close project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": nwTempGUI}, True)
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
assert nwGUI.saveProject()
assert nwGUI.closeProject()
@@ -50,14 +52,17 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
assert not nwGUI.theProject.spellCheck
# Check the files
projFile = path.join(nwTempGUI, "nwProject.nwx")
assert cmpFiles(projFile, path.join(nwRef, "gui", "0_nwProject.nwx"), [2, 6, 7, 8])
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempGUI, "0_nwProject.nwx")
refFile = path.join(nwRef, "gui", "0_nwProject.nwx")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
qtbot.wait(stepDelay)
# qtbot.stopForInteraction()
# Re-open project
assert nwGUI.openProject(nwTempGUI)
assert nwGUI.openProject(nwFuncTemp)
qtbot.wait(stepDelay)
# Check that we loaded the data
@@ -65,8 +70,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
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 == nwTempGUI
assert nwGUI.theProject.projMeta == path.join(nwTempGUI, "meta")
assert nwGUI.theProject.projPath == nwFuncTemp
assert nwGUI.theProject.projMeta == path.join(nwFuncTemp, "meta")
assert nwGUI.theProject.projFile == "nwProject.nwx"
assert nwGUI.theProject.projName == "New Project"
assert nwGUI.theProject.bookTitle == ""
@@ -266,23 +271,42 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
assert nwGUI.saveProject()
# Check the files
refFile = path.join(nwTempGUI, "nwProject.nwx")
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2, 6, 7, 8])
refFile = path.join(nwTempGUI, "content", "031b4af5197ec.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_031b4af5197ec.nwd"))
refFile = path.join(nwTempGUI, "content", "1a6562590ef19.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_1a6562590ef19.nwd"))
refFile = path.join(nwTempGUI, "content", "0e17daca5f3e1.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd"))
refFile = path.join(nwTempGUI, "content", "41cfc0d1f2d12.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_41cfc0d1f2d12.nwd"))
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempGUI, "1_nwProject.nwx")
refFile = path.join(nwRef, "gui", "1_nwProject.nwx")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
projFile = path.join(nwFuncTemp, "content", "031b4af5197ec.nwd")
testFile = path.join(nwTempGUI, "1_031b4af5197ec.nwd")
refFile = path.join(nwRef, "gui", "1_031b4af5197ec.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwFuncTemp, "content", "1a6562590ef19.nwd")
testFile = path.join(nwTempGUI, "1_1a6562590ef19.nwd")
refFile = path.join(nwRef, "gui", "1_1a6562590ef19.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwFuncTemp, "content", "0e17daca5f3e1.nwd")
testFile = path.join(nwTempGUI, "1_0e17daca5f3e1.nwd")
refFile = path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwFuncTemp, "content", "41cfc0d1f2d12.nwd")
testFile = path.join(nwTempGUI, "1_41cfc0d1f2d12.nwd")
refFile = path.join(nwRef, "gui", "1_41cfc0d1f2d12.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
nwGUI.closeMain()
# qtbot.stopForInteraction()
@pytest.mark.gui
def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -290,8 +314,8 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
# Create new, save, open project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": nwTempGUI}, True)
nwGUI.mainConf.backupPath = nwTempGUI
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
nwGUI.mainConf.backupPath = nwFuncTemp
projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
projEdit.show()
@@ -369,15 +393,18 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
qtbot.wait(stepDelay)
# Check the files
projFile = path.join(nwTempGUI, "nwProject.nwx")
assert cmpFiles(projFile, path.join(nwRef, "gui", "2_nwProject.nwx"), [2, 8, 9, 10])
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempGUI, "2_nwProject.nwx")
refFile = path.join(nwRef, "gui", "2_nwProject.nwx")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
# qtbot.stopForInteraction()
nwGUI.closeMain()
@pytest.mark.gui
def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -385,7 +412,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
# Create new, save, open project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": nwTempGUI}, True)
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
assert nwGUI.openDocument("0e17daca5f3e1")
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
@@ -425,24 +452,70 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
qtbot.wait(stepDelay)
# Check the files
projFile = path.join(nwTempGUI, "nwProject.nwx")
assert cmpFiles(projFile, path.join(nwRef, "gui", "3_nwProject.nwx"), [2, 6, 7, 8])
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempGUI, "3_nwProject.nwx")
refFile = path.join(nwRef, "gui", "3_nwProject.nwx")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
nwGUI.closeMain()
# qtbot.stopForInteraction()
@pytest.mark.gui
def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
assert nwGUI.openProject(nwTempGUI)
# Create new, save, close project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
assert nwGUI.saveProject()
assert nwGUI.closeProject()
qtbot.wait(stepDelay)
nwGUI.mainConf.lastPath = nwTempGUI
assert nwGUI.openProject(nwFuncTemp)
qtbot.wait(stepDelay)
# Add some text to the scene file
assert nwGUI.openDocument("0e17daca5f3e1")
assert nwGUI.docEditor.insertText(
"# Scene One\n\n"
"It was the best of times, it was the worst of times, it was the age of wisdom, it was "
"the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it "
"was the season of Light, it was the season of Darkness, it was the spring of hope, it "
"was the winter of despair, we had everything before us, we had nothing before us, we "
"were all going direct to Heaven, we were all going direct the other way in short, the "
"period was so far like the present period, that some of its noisiest authorities "
"insisted on its being received, for good or for evil, in the superlative degree of "
"comparison only.\n\n"
)
assert nwGUI.saveDocument()
# Add a note file with some text
nwGUI.setFocus(1)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
assert nwGUI.openSelectedItem()
assert nwGUI.docEditor.insertText(
"# Jane Doe\n\n"
"All about Jane.\n\n"
)
assert nwGUI.saveDocument()
qtbot.wait(500) # Ensures that the session length is > 0
assert nwGUI.saveProject()
assert nwGUI.closeProject()
qtbot.wait(stepDelay)
# Open again, and check the stats
assert nwGUI.openProject(nwFuncTemp)
qtbot.wait(stepDelay)
nwGUI.mainConf.lastPath = nwFuncTemp
sessLog = GuiWritingStats(nwGUI, nwGUI.theProject)
sessLog.show()
qtbot.wait(stepDelay)
@@ -452,15 +525,15 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
jsonStats = path.join(nwTempGUI, "sessionStats.json")
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
assert len(jsonData) == 3
assert jsonData[1]["length"] > 0
assert jsonData[1]["newWords"] == 84
assert jsonData[1]["novelWords"] == 63
assert jsonData[1]["noteWords"] == 27
assert len(jsonData) == 2
assert jsonData[1]["length"] >= 0
assert jsonData[1]["newWords"] == 126
assert jsonData[1]["novelWords"] == 127
assert jsonData[1]["noteWords"] == 5
# No Novel Files
qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
@@ -468,15 +541,15 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
jsonStats = path.join(nwTempGUI, "sessionStats.json")
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
assert len(jsonData) == 2
assert jsonData[0]["length"] > 0
assert jsonData[0]["newWords"] == 27
assert jsonData[0]["novelWords"] == 63
assert jsonData[0]["noteWords"] == 27
assert len(jsonData) == 1
assert jsonData[0]["length"] >= 0
assert jsonData[0]["newWords"] == 5
assert jsonData[0]["novelWords"] == 127
assert jsonData[0]["noteWords"] == 5
# No Note Files
qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
@@ -485,15 +558,15 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
jsonStats = path.join(nwTempGUI, "sessionStats.json")
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
assert len(jsonData) == 3
assert jsonData[1]["length"] > 0
assert jsonData[1]["newWords"] == 57
assert jsonData[1]["novelWords"] == 63
assert jsonData[1]["noteWords"] == 27
assert len(jsonData) == 2
assert jsonData[1]["length"] >= 0
assert jsonData[1]["newWords"] == 121
assert jsonData[1]["novelWords"] == 127
assert jsonData[1]["noteWords"] == 5
# No Negative Entries
qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
@@ -502,7 +575,7 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
jsonStats = path.join(nwTempGUI, "sessionStats.json")
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
@@ -515,11 +588,11 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
jsonStats = path.join(nwTempGUI, "sessionStats.json")
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
assert len(jsonData) == 4
assert len(jsonData) == 2
# Group by Day
qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
@@ -527,7 +600,7 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
assert sessLog._saveData(sessLog.FMT_JSON)
qtbot.wait(stepDelay)
jsonStats = path.join(nwTempGUI, "sessionStats.json")
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
jsonData = json.loads(inFile.read())
@@ -541,8 +614,8 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
def testAboutBox(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testAboutBox(qtbot, nwFuncTemp, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -553,14 +626,13 @@ def testAboutBox(qtbot, nwTempGUI, nwRef, nwTemp):
assert msgAbout.pageLicense.document().characterCount() > 100
# qtbot.stopForInteraction()
msgAbout._doClose()
nwGUI.closeMain()
@pytest.mark.gui
def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempBuild, "--data=%s" % nwTemp])
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -568,7 +640,7 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
assert nwGUI.openProject(nwLipsum)
nwGUI.mainConf.lastPath = nwTempBuild
nwGUI.mainConf.lastPath = nwLipsum
nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
@@ -578,10 +650,17 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
refFile = path.join(nwTempBuild, "Lorem Ipsum.nwd")
assert cmpFiles(refFile, path.join(nwRef, "build", "1_LoremIpsum.nwd"), [])
refFile = path.join(nwTempBuild, "Lorem Ipsum.htm")
assert cmpFiles(refFile, path.join(nwRef, "build", "1_LoremIpsum.htm"), [])
projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
testFile = path.join(nwTempBuild, "1_LoremIpsum.nwd")
refFile = path.join(nwRef, "build", "1_LoremIpsum.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
testFile = path.join(nwTempBuild, "1_LoremIpsum.htm")
refFile = path.join(nwRef, "build", "1_LoremIpsum.htm")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# Change Title Formats and Flip Switches
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
@@ -610,10 +689,17 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
refFile = path.join(nwTempBuild, "Lorem Ipsum.nwd")
assert cmpFiles(refFile, path.join(nwRef, "build", "2_LoremIpsum.nwd"), [])
refFile = path.join(nwTempBuild, "Lorem Ipsum.htm")
assert cmpFiles(refFile, path.join(nwRef, "build", "2_LoremIpsum.htm"), [])
projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
testFile = path.join(nwTempBuild, "2_LoremIpsum.nwd")
refFile = path.join(nwRef, "build", "2_LoremIpsum.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
testFile = path.join(nwTempBuild, "2_LoremIpsum.htm")
refFile = path.join(nwRef, "build", "2_LoremIpsum.htm")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# Putline Mode
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
@@ -637,19 +723,26 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
refFile = path.join(nwTempBuild, "Lorem Ipsum.nwd")
assert cmpFiles(refFile, path.join(nwRef, "build", "3_LoremIpsum.nwd"), [])
refFile = path.join(nwTempBuild, "Lorem Ipsum.htm")
assert cmpFiles(refFile, path.join(nwRef, "build", "3_LoremIpsum.htm"), [])
projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
testFile = path.join(nwTempBuild, "3_LoremIpsum.nwd")
refFile = path.join(nwRef, "build", "3_LoremIpsum.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
testFile = path.join(nwTempBuild, "3_LoremIpsum.htm")
refFile = path.join(nwRef, "build", "3_LoremIpsum.htm")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# qtbot.stopForInteraction()
nwBuild._doClose()
nwGUI.closeMain()
@pytest.mark.gui
def testMergeTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -670,24 +763,11 @@ def testMergeTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
assert nwGUI.theProject.projTree["73475cb40a568"] is not None
refFile = path.join(nwLipsum, "content", "73475cb40a568.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "4_73475cb40a568.nwd"))
# qtbot.stopForInteraction()
nwGUI.closeMain()
@pytest.mark.gui
def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum)
qtbot.wait(stepDelay)
projFile = path.join(nwLipsum, "content", "73475cb40a568.nwd")
testFile = path.join(nwTempGUI, "4_73475cb40a568.nwd")
refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# Split By Chapter
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
@@ -701,8 +781,11 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
assert nwGUI.theProject.projTree["71ee45a3c0db9"] is not None
# This should give us back the file as it was before
refFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "4_73475cb40a568.nwd"), [1])
projFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
testFile = path.join(nwTempGUI, "4_71ee45a3c0db9.nwd")
refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [1])
# Split By Scene
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
@@ -718,12 +801,23 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
assert nwGUI.theProject.projTree["31489056e0916"] is not None
assert nwGUI.theProject.projTree["98010bd9270f9"] is not None
refFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd"))
refFile = path.join(nwLipsum, "content", "31489056e0916.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_31489056e0916.nwd"))
refFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_98010bd9270f9.nwd"))
projFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "content", "31489056e0916.nwd")
testFile = path.join(nwTempGUI, "5_31489056e0916.nwd")
refFile = path.join(nwRef, "gui", "5_31489056e0916.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd")
testFile = path.join(nwTempGUI, "5_98010bd9270f9.nwd")
refFile = path.join(nwRef, "gui", "5_98010bd9270f9.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# Split By Section
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
@@ -741,22 +835,41 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
assert nwGUI.theProject.projTree["2fca346db6561"] is not None
refFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd"), [1])
refFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_031b4af5197ec.nwd"))
refFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd"))
refFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_2858dcd1057d3.nwd"))
refFile = path.join(nwLipsum, "content", "2fca346db6561.nwd")
assert cmpFiles(refFile, path.join(nwRef, "gui", "5_2fca346db6561.nwd"))
projFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd")
testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [1])
projFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd")
testFile = path.join(nwTempGUI, "5_031b4af5197ec.nwd")
refFile = path.join(nwRef, "gui", "5_031b4af5197ec.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
testFile = path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd")
refFile = path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
testFile = path.join(nwTempGUI, "5_2858dcd1057d3.nwd")
refFile = path.join(nwRef, "gui", "5_2858dcd1057d3.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
projFile = path.join(nwLipsum, "content", "2fca346db6561.nwd")
testFile = path.join(nwTempGUI, "5_2fca346db6561.nwd")
refFile = path.join(nwRef, "gui", "5_2fca346db6561.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# qtbot.stopForInteraction()
nwGUI.closeMain()
@pytest.mark.gui
def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
from PyQt5.QtWidgets import QWizard
from nw.gui.projwizard import (
@@ -764,7 +877,7 @@ def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
ProjWizardCustomPage, ProjWizardFinalPage
)
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -885,9 +998,9 @@ def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
def testDocAction(qtbot, nwLipsum, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -1001,8 +1114,8 @@ def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testInsertMenu(qtbot, nwFuncTemp, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -1049,6 +1162,10 @@ def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp):
assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[1]
nwGUI.docEditor.clear()
nwGUI.mainMenu.aInsMSApos.activate(QAction.Trigger)
assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS
nwGUI.docEditor.clear()
nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger)
assert nwGUI.docEditor.getText() == " \n"
nwGUI.docEditor.clear()
@@ -1075,24 +1192,29 @@ def testInsertMenu(qtbot, nwTempGUI, nwFuncTemp, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
def testLoadProject(qtbot, nwTempGUI, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
def testLoadProject(qtbot, nwMinimal, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
assert nwGUI.openProject(nwMinimal)
assert nwGUI.closeProject()
nwLoad = GuiProjectLoad(nwGUI)
nwLoad.show()
recentCount = nwLoad.listBox.topLevelItemCount()
assert recentCount > 1
assert recentCount > 0
selItem = nwLoad.listBox.topLevelItem(1)
selItem = nwLoad.listBox.topLevelItem(0)
selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
assert isinstance(selItem, QTreeWidgetItem)
nwLoad.selPath.setText("")
nwLoad.listBox.setCurrentItem(selItem)
nwLoad._doSelectRecent()
assert nwLoad.selPath.text() == selPath
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
@@ -1121,16 +1243,16 @@ def testLoadProject(qtbot, nwTempGUI, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
def testOutline(qtbot, nwTempBuild, nwLipsum, nwTemp):
def testOutline(qtbot, nwLipsum, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwTempBuild, "--data=%s" % nwTemp])
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
assert nwGUI.openProject(nwLipsum)
nwGUI.mainConf.lastPath = nwTempBuild
nwGUI.mainConf.lastPath = nwLipsum
nwGUI.rebuildIndex()
nwGUI.tabWidget.setCurrentIndex(nwGUI.idxTabProj)
+48 -26
View File
@@ -5,22 +5,15 @@
import pytest
from lxml import etree
from nwdummy import DummyMain
from nw.config import Config
from nw.core.project import NWProject, NWItem
from nw.constants import nwItemClass, nwItemType, nwItemLayout
theConf = Config()
theMain = DummyMain()
theMain.mainConf = theConf
theProject = NWProject(theMain)
theItem = NWItem(theProject)
nwXML = etree.Element("novelWriterXML")
@pytest.mark.project
def testItemSettersSimple():
def testItemSettersSimple(nwDummy):
theProject = NWProject(nwDummy)
theItem = NWItem(theProject)
# Name
theItem.setName("A Name")
@@ -113,7 +106,10 @@ def testItemSettersSimple():
assert theItem.cursorPos == 1
@pytest.mark.project
def testItemClassSetter():
def testItemClassSetter(nwDummy):
theProject = NWProject(nwDummy)
theItem = NWItem(theProject)
# Class
theItem.setClass(None)
@@ -138,13 +134,18 @@ def testItemClassSetter():
assert theItem.itemClass == nwItemClass.ENTITY
theItem.setClass("CUSTOM")
assert theItem.itemClass == nwItemClass.CUSTOM
theItem.setClass("ARCHIVE")
assert theItem.itemClass == nwItemClass.ARCHIVE
theItem.setClass("TRASH")
assert theItem.itemClass == nwItemClass.TRASH
@pytest.mark.project
def testItemTypeSetter():
def testItemTypeSetter(nwDummy):
# Class
theProject = NWProject(nwDummy)
theItem = NWItem(theProject)
# Type
theItem.setType(None)
assert theItem.itemType == nwItemType.NO_TYPE
theItem.setType("NONSENSE")
@@ -161,9 +162,12 @@ def testItemTypeSetter():
assert theItem.itemType == nwItemType.TRASH
@pytest.mark.project
def testItemLayoutSetter():
def testItemLayoutSetter(nwDummy):
# Class
theProject = NWProject(nwDummy)
theItem = NWItem(theProject)
# Layout
theItem.setLayout(None)
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
theItem.setLayout("NONSENSE")
@@ -188,7 +192,25 @@ def testItemLayoutSetter():
assert theItem.itemLayout == nwItemLayout.NOTE
@pytest.mark.project
def testItemXMLPackUnpack():
def testItemXMLPackUnpack(nwDummy):
theProject = NWProject(nwDummy)
theItem = NWItem(theProject)
nwXML = etree.Element("novelWriterXML")
theItem.setHandle("0123456789abc")
theItem.setParent("0123456789abc")
theItem.setOrder(1)
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FILE")
theItem.setStatus("Main")
theItem.setLayout("NOTE")
theItem.setExpanded(True)
theItem.setParaCount(3)
theItem.setWordCount(5)
theItem.setCharCount(7)
theItem.setCursorPos(11)
# Pack
xContent = etree.SubElement(nwXML, "content")
@@ -196,9 +218,9 @@ def testItemXMLPackUnpack():
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b"<content>"
b"<item handle=\"0123456789abc\" order=\"1\" parent=\"0123456789abc\">"
b"<name>A Name</name><type>TRASH</type><class>TRASH</class>"
b"<status>Main</status><expanded>True</expanded>"
b"</item>"
b"<name>A Name</name><type>FILE</type><class>NOVEL</class><status>New</status>"
b"<exported>True</exported><layout>NOTE</layout><charCount>7</charCount>"
b"<wordCount>5</wordCount><paraCount>3</paraCount><cursorPos>11</cursorPos></item>"
b"</content>"
)
@@ -208,10 +230,10 @@ def testItemXMLPackUnpack():
assert theItem.parHandle == "0123456789abc"
assert theItem.itemOrder == 1
assert theItem.isExpanded
assert theItem.charCount == 1
assert theItem.wordCount == 1
assert theItem.paraCount == 1
assert theItem.cursorPos == 1
assert theItem.itemClass == nwItemClass.TRASH
assert theItem.itemType == nwItemType.TRASH
assert theItem.paraCount == 3
assert theItem.wordCount == 5
assert theItem.charCount == 7
assert theItem.cursorPos == 11
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE
+100 -71
View File
@@ -4,63 +4,66 @@
import pytest
from os import path
from shutil import copyfile
from nwtools import cmpFiles
from nwdummy import DummyMain
from nw.config import Config
from nw.core.project import NWProject
from nw.core.index import NWIndex
from nw.constants import nwItemClass
theConf = Config()
theMain = DummyMain()
theMain.mainConf = theConf
theProject = NWProject(theMain)
theProject.projTree.setSeed(42)
@pytest.mark.project
def testProjectNewMinimal(nwTempProj, nwRef, nwTemp):
projFile = path.join(nwTempProj, "nwProject.nwx")
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempProj, "1_nwProject.nwx")
refFile = path.join(nwRef, "proj", "1_nwProject.nwx")
assert theConf.initConfig(nwRef, nwTemp)
assert theProject.newProject({"projPath": nwTempProj})
assert theProject.setProjectPath(nwTempProj)
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": nwFuncTemp})
assert theProject.setProjectPath(nwFuncTemp)
assert theProject.saveProject()
assert theProject.closeProject()
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
@pytest.mark.project
def testProjectOpen(nwTempProj):
projFile = path.join(nwTempProj, "nwProject.nwx")
# Check the new project
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
# Open again
assert theProject.openProject(projFile)
@pytest.mark.project
def testProjectSave(nwTempProj, nwRef):
projFile = path.join(nwTempProj, "nwProject.nwx")
refFile = path.join(nwRef, "proj", "1_nwProject.nwx")
# Save and close
assert theProject.saveProject()
assert theProject.closeProject()
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
assert not theProject.projChanged
@pytest.mark.project
def testProjectOpenTwice(nwTempProj, nwRef):
projFile = path.join(nwTempProj, "nwProject.nwx")
refFile = path.join(nwRef, "proj", "1_nwProject.nwx")
# Open a second time
assert theProject.openProject(projFile)
assert not theProject.openProject(projFile)
assert theProject.openProject(projFile, overrideLock=True)
assert theProject.saveProject()
assert theProject.closeProject()
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
@pytest.mark.project
def testProjectNewRoot(nwTempProj, nwRef):
projFile = path.join(nwTempProj, "nwProject.nwx")
def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy):
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempProj, "2_nwProject.nwx")
refFile = path.join(nwRef, "proj", "2_nwProject.nwx")
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": nwFuncTemp})
assert theProject.setProjectPath(nwFuncTemp)
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))
@@ -69,31 +72,48 @@ def testProjectNewRoot(nwTempProj, nwRef):
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()
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
assert not theProject.projChanged
@pytest.mark.project
def testProjectNewFile(nwTempProj, nwRef):
projFile = path.join(nwTempProj, "nwProject.nwx")
def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy):
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempProj, "3_nwProject.nwx")
refFile = path.join(nwRef, "proj", "3_nwProject.nwx")
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": nwFuncTemp})
assert theProject.setProjectPath(nwFuncTemp)
assert theProject.saveProject()
assert theProject.closeProject()
assert theProject.openProject(projFile)
assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "73475cb40a568"), str)
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()
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
assert not theProject.projChanged
@pytest.mark.project
def testIndexScanThis(nwTempProj):
projFile = path.join(nwTempProj, "nwProject.nwx")
assert theProject.openProject(projFile)
def testIndexScanThis(nwMinimal, nwDummy):
theIndex = NWIndex(theProject, theMain)
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal)
theIndex = NWIndex(theProject, nwDummy)
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
assert not isValid
@@ -135,15 +155,17 @@ def testIndexScanThis(nwTempProj):
assert theProject.closeProject()
@pytest.mark.project
def testIndexCheckThese(nwTempProj):
projFile = path.join(nwTempProj, "nwProject.nwx")
assert theProject.openProject(projFile)
def testIndexCheckThese(nwMinimal, nwDummy):
theIndex = NWIndex(theProject, theMain)
nHandle = "0e17daca5f3e1"
nItem = theProject.projTree[nHandle]
cHandle = "02d20bbd7e394"
cItem = theProject.projTree[cHandle]
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal)
theIndex = NWIndex(theProject, nwDummy)
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
nItem = theProject.projTree[nHandle]
cItem = theProject.projTree[cHandle]
assert theIndex.scanText(cHandle, (
"# Jane Smith\n"
@@ -153,7 +175,7 @@ def testIndexCheckThese(nwTempProj):
"# Hello World!\n"
"@pov: Jane"
))
assert str(theIndex.tagIndex) == "{'Jane': [2, '02d20bbd7e394', 'CHARACTER', 'T000001']}"
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
@@ -168,13 +190,15 @@ def testIndexCheckThese(nwTempProj):
assert theProject.closeProject()
@pytest.mark.project
def testIndexMeta(nwTempProj):
projFile = path.join(nwTempProj, "nwProject.nwx")
assert theProject.openProject(projFile)
def testIndexMeta(nwMinimal, nwDummy):
theIndex = NWIndex(theProject, theMain)
nHandle = "0e17daca5f3e1"
cHandle = "02d20bbd7e394"
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal)
theIndex = NWIndex(theProject, nwDummy)
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
assert theIndex.scanText(cHandle, (
"# Jane Smith\n"
@@ -191,11 +215,11 @@ def testIndexMeta(nwTempProj):
"\n"
"Well, not really.\n"
))
assert str(theIndex.tagIndex) == "{'Jane': [2, '02d20bbd7e394', 'CHARACTER', 'T000001']}"
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
# The novel structure should contain the pointer to the novel file header
assert str(theIndex.getNovelStructure()) == "['0e17daca5f3e1:T000001']"
assert str(theIndex.getNovelStructure()) == "['%s:T000001']" % nHandle
# The novel file should have the correct counts
cC, wC, pC = theIndex.getCounts(nHandle)
@@ -210,21 +234,22 @@ def testIndexMeta(nwTempProj):
# The character file should have a record of the reference from the novel file
theRefs = theIndex.getBackReferenceList(cHandle)
assert str(theRefs) == "{'0e17daca5f3e1': 'T000001'}"
assert str(theRefs) == "{'%s': 'T000001'}" % nHandle
assert theProject.closeProject()
# The two following tests must be at the end as they mess up the config object
# and the handle seed. They go into their own folders, but use the same project
# object as the test above.
@pytest.mark.project
def testProjectNewCustom(nwTempCustom, nwRef, nwTemp):
def testProjectNewCustom(nwFuncTemp, nwTempProj, nwRef, nwDummy):
projFile = path.join(nwFuncTemp, "nwProject.nwx")
testFile = path.join(nwTempProj, "4_nwProject.nwx")
refFile = path.join(nwRef, "proj", "4_nwProject.nwx")
projData = {
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
"projPath": nwTempCustom,
"projPath": nwFuncTemp,
"popSample": False,
"popMinimal": False,
"popCustom": True,
@@ -240,29 +265,33 @@ def testProjectNewCustom(nwTempCustom, nwRef, nwTemp):
"numScenes": 3,
"chFolders": True,
}
theProject.mainConf = theConf
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
assert theProject.newProject(projData)
assert theProject.saveProject()
assert theProject.closeProject()
projFile = path.join(nwTempCustom, "nwProject.nwx")
refFile = path.join(nwRef, "proj", "4_nwProject.nwx")
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
@pytest.mark.project
def testProjectNewSample(nwTempSample, nwLipsum, nwRef, nwTemp):
def testProjectNewSample(nwFuncTemp, nwRef, nwConf, nwDummy):
projData = {
"projName": "Test Sample",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
"projPath": nwTempSample,
"projPath": nwFuncTemp,
"popSample": True,
"popMinimal": False,
"popCustom": False,
}
theProject.mainConf = theConf
theProject = NWProject(nwDummy)
theProject.projTree.setSeed(42)
theProject.mainConf = nwConf
assert theProject.newProject(projData)
assert theProject.openProject(nwTempSample)
assert theProject.openProject(nwFuncTemp)
assert theProject.projName == "Sample Project"
assert theProject.saveProject()
assert theProject.closeProject()