From 36be7b0b339a473a98eb71a4e4f5f280300dda62 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 5 Jul 2021 16:10:52 +0200
Subject: [PATCH] Test Coverage of the Editor (#787)
* Drop the _qDocument class variables
* Split up the current doceditor test file
* Add tests for editor init, load and save
* Add tests for editor meta functions and actions
* Add tests for editor insert functions
* Add tests for editor text manipulation and block formatting
* Fix code style
* Add text coverage for alignment and indent
---
nw/gui/doceditor.py | 680 ++++++------
nw/gui/dochighlight.py | 6 +-
nw/gui/docviewer.py | 13 +-
nw/tools/build.py | 19 +-
tests/conftest.py | 59 ++
tests/test_gui/test_gui_doceditor.py | 1418 ++++++++++++++++++++------
tests/test_gui/test_gui_guimain.py | 354 +++++++
7 files changed, 1892 insertions(+), 657 deletions(-)
create mode 100644 tests/test_gui/test_gui_guimain.py
diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py
index 8e203a41..7574d49e 100644
--- a/nw/gui/doceditor.py
+++ b/nw/gui/doceditor.py
@@ -111,9 +111,9 @@ class GuiDocEditor(QTextEdit):
self._typPadChar = " "
# Core Elements and Signals
- self._qDocument = self.document()
- self._qDocument.contentsChange.connect(self._docChange)
- self._qDocument.documentLayout().documentSizeChanged.connect(self._docSizeChanged)
+ qDoc = self.document()
+ qDoc.contentsChange.connect(self._docChange)
+ qDoc.documentLayout().documentSizeChanged.connect(self._docSizeChanged)
# Document Title
self.docHeader = GuiDocEditHeader(self)
@@ -121,7 +121,7 @@ class GuiDocEditor(QTextEdit):
self.docSearch = GuiDocEditSearch(self)
# Syntax
- self.hLight = GuiDocHighlighter(self._qDocument, self.theParent)
+ self.hLight = GuiDocHighlighter(qDoc, self.theParent)
# Context Menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
@@ -221,9 +221,10 @@ class GuiDocEditor(QTextEdit):
# Set font
theFont = QFont()
+ qDoc = self.document()
if self.mainConf.textFont is None:
# If none is defined, set the default back to config
- self.mainConf.textFont = self._qDocument.defaultFont().family()
+ self.mainConf.textFont = qDoc.defaultFont().family()
theFont.setFamily(self.mainConf.textFont)
theFont.setPointSize(self.mainConf.textSize)
@@ -246,7 +247,7 @@ class GuiDocEditor(QTextEdit):
# Set default text margins
cM = self.mainConf.getTextMargin()
- self._qDocument.setDocumentMargin(0)
+ qDoc.setDocumentMargin(0)
self.setViewportMargins(cM, cM, cM, cM)
# Also set the document text options for the document text flow
@@ -259,7 +260,7 @@ class GuiDocEditor(QTextEdit):
if self.mainConf.showLineEndings:
theOpt.setFlags(theOpt.flags() | QTextOption.ShowLineAndParagraphSeparators)
- self._qDocument.setDefaultTextOption(theOpt)
+ qDoc.setDefaultTextOption(theOpt)
# Scroll bars
if self.mainConf.hideVScroll:
@@ -275,7 +276,7 @@ class GuiDocEditor(QTextEdit):
# Refresh the tab stops
if self.mainConf.verQtValue >= 51000:
self.setTabStopDistance(self.mainConf.getTabWidth())
- else:
+ else: # pragma: no cover
self.setTabStopWidth(self.mainConf.getTabWidth())
# Initialise the syntax highlighter
@@ -370,7 +371,6 @@ class GuiDocEditor(QTextEdit):
self.setCursorLine(tLine)
self.docFooter.updateLineCount()
- self.lengthLast = self._qDocument.characterCount()
self._docHeaders = self.theIndex.getHandleHeaders(self._docHandle)
qApp.processEvents()
@@ -378,7 +378,7 @@ class GuiDocEditor(QTextEdit):
qApp.restoreOverrideCursor()
# This is a hack to fix invisble cursor on an empty document
- if self._qDocument.characterCount() <= 1:
+ if self.document().characterCount() <= 1:
self.setPlainText("\n")
self.setPlainText("")
self.setCursorPosition(0)
@@ -391,7 +391,7 @@ class GuiDocEditor(QTextEdit):
return True
- def updateTagHighLighting(self, forceBigDoc=False):
+ def updateTagHighLighting(self):
"""Rerun the syntax highlighter on all meta data lines.
"""
self.hLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
@@ -400,7 +400,7 @@ class GuiDocEditor(QTextEdit):
def redrawText(self):
"""Redraw the text by marking the document content as "dirty".
"""
- self._qDocument.markContentsDirty(0, self._qDocument.characterCount())
+ self.document().markContentsDirty(0, self.document().characterCount())
self.updateDocMargins()
return
@@ -532,9 +532,9 @@ class GuiDocEditor(QTextEdit):
tmpDocChanged = self._docChanged
if self.mainConf.scrollPastEnd:
- docFrame = self._qDocument.rootFrame().frameFormat()
+ docFrame = self.document().rootFrame().frameFormat()
docFrame.setBottomMargin(max(0, 0.9*(wH - uM - lM - 4*tB)))
- self._qDocument.rootFrame().setFrameFormat(docFrame)
+ self.document().rootFrame().setFrameFormat(docFrame)
# This is needed as the setFrameFormat function itself will
# trigger the contetsChanged signal which sets _docChanged, so we
@@ -576,7 +576,7 @@ class GuiDocEditor(QTextEdit):
def isEmpty(self):
"""Wrapper function to check if the current document is empty.
"""
- return self._qDocument.isEmpty()
+ return self.document().isEmpty()
def currentDictionary(self):
"""Return the current dictionary object.
@@ -596,7 +596,7 @@ class GuiDocEditor(QTextEdit):
See: https://doc.qt.io/qt-5/qtextdocument.html#toPlainText
"""
if self.mainConf.verQtValue >= 50900:
- theText = self._qDocument.toRawText()
+ theText = self.document().toRawText()
theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
else:
@@ -628,7 +628,7 @@ class GuiDocEditor(QTextEdit):
if not isinstance(thePosition, int):
return False
- nChars = self._qDocument.characterCount()
+ nChars = self.document().characterCount()
if nChars > 1:
theCursor = self.textCursor()
theCursor.setPosition(min(max(thePosition, 0), nChars-1))
@@ -652,7 +652,7 @@ class GuiDocEditor(QTextEdit):
return False
if theLine >= 0:
- theBlock = self._qDocument.findBlockByLineNumber(theLine)
+ theBlock = self.document().findBlockByLineNumber(theLine)
if theBlock:
self.setCursorPosition(theBlock.position())
self.docFooter.updateLineCount()
@@ -710,7 +710,8 @@ class GuiDocEditor(QTextEdit):
def spellCheckDocument(self):
"""Rerun the highlighter to update spell checking status of the
currently loaded text. The fastest way to do this, at least as
- of Qt 5.13, is to clear the text and put it back.
+ of Qt 5.13, is to clear the text and put it back. This clears
+ the undo stack, so we only do it for big documents.
"""
logger.verbose("Running spell checker")
if self._spellCheck:
@@ -739,11 +740,16 @@ class GuiDocEditor(QTextEdit):
passed to it without having to consider the internal logic of
this class when calling these actions from other classes.
"""
- logger.verbose("Requesting action: '%s'", theAction.name)
if self._docHandle is None:
logger.error("No document open")
return False
+ if not isinstance(theAction, nwDocAction):
+ logger.error("Not a document action")
+ return False
+
+ logger.verbose("Requesting action: %s" % theAction.name)
+
self._allowAutoReplace(False)
if theAction == nwDocAction.UNDO:
self.undo()
@@ -1012,7 +1018,7 @@ class GuiDocEditor(QTextEdit):
self._lastEdit = time()
self._lastFind = None
- if self._qDocument.characterCount() > nwConst.MAX_DOCSIZE:
+ if self.document().characterCount() > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert(self.tr(
"The document has grown too big and you cannot add more text to it. "
"The maximum size of a single novelWriter document is {0} MB."
@@ -1029,7 +1035,7 @@ class GuiDocEditor(QTextEdit):
self.wcTimer.start()
if self._doReplace and chrAdd == 1:
- self._docAutoReplace(self._qDocument.findBlock(thePos))
+ self._docAutoReplace(self.document().findBlock(thePos))
return
@@ -1198,7 +1204,7 @@ class GuiDocEditor(QTextEdit):
# Must not be emitted if docHandle is None!
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
- self._checkDocSize(self._qDocument.characterCount())
+ self._checkDocSize(self.document().characterCount())
self.docFooter.updateCounts()
return
@@ -1211,7 +1217,7 @@ class GuiDocEditor(QTextEdit):
moved to has been drawn before the move is made.
"""
if self._queuePos is not None:
- thePos = self._qDocument.documentLayout().hitTest(
+ thePos = self.document().documentLayout().hitTest(
QPointF(theSize.width(), theSize.height()), Qt.FuzzyHit
)
if self._queuePos <= thePos:
@@ -1362,226 +1368,74 @@ class GuiDocEditor(QTextEdit):
return
##
- # Internal Functions
+ # Internal Functions : Text Manipulation
##
- def _followTag(self, theCursor=None, loadTag=True):
- """Activated by Ctrl+Enter. Checks that we're in a block
- starting with '@'. We then find the word under the cursor and
- check that it is after the ':'. If all this is fine, we have a
- tag and can tell the document viewer to try and find and load
- the file where the tag is defined.
+ def _toggleFormat(self, fLen, fChar):
+ """Toggle the formatting of a specific type for a piece of text.
+ If more than one block is selected, the formatting is applied to
+ the first block.
"""
- if theCursor is None:
- theCursor = self.textCursor()
-
- theBlock = theCursor.block()
- theText = theBlock.text()
-
- if len(theText) == 0:
+ theCursor = self._autoSelect()
+ if not theCursor.hasSelection():
+ logger.warning("No selection made, nothing to do")
return False
- if theText.startswith("@"):
+ posS = theCursor.selectionStart()
+ posE = theCursor.selectionEnd()
- theCursor.select(QTextCursor.WordUnderCursor)
- theWord = theCursor.selectedText()
- cPos = theText.find(":")
- wPos = theCursor.selectionStart() - theBlock.position()
- if wPos <= cPos:
- return False
+ blockS = self.document().findBlock(posS)
+ blockE = self.document().findBlock(posE)
- if loadTag:
- logger.verbose("Attempting to follow tag '%s'", theWord)
- self.theParent.docViewer.loadFromTag(theWord)
+ if blockS != blockE:
+ posE = blockS.position() + blockS.length() - 1
+ theCursor.clearSelection()
+ theCursor.setPosition(posS, QTextCursor.MoveAnchor)
+ theCursor.setPosition(posE, QTextCursor.KeepAnchor)
+ self.setTextCursor(theCursor)
+
+ numB = 0
+ for n in range(fLen):
+ if self.document().characterAt(posS-n-1) == fChar:
+ numB += 1
else:
- logger.verbose("Potential tag '%s'", theWord)
+ break
- return True
-
- return False
-
- def _openSpellContext(self):
- """Opens the spell check context menu at the current point of
- the cursor.
- """
- self._openContextMenu(self.cursorRect().center())
- return
-
- def _docAutoReplace(self, theBlock):
- """Auto-replace text elements based on main configuration.
- """
- if not theBlock.isValid():
- return
-
- theText = theBlock.text()
- theCursor = self.textCursor()
- thePos = theCursor.positionInBlock()
- theLen = len(theText)
-
- if theLen < 1 or thePos-1 > theLen:
- return
-
- theOne = theText[thePos-1:thePos]
- theTwo = theText[thePos-2:thePos]
- theThree = theText[thePos-3:thePos]
-
- if not theOne: # Makes Neo sad
- return
-
- nDelete = 0
- tInsert = theOne
-
- if self.mainConf.doReplaceDQuote and theTwo == ' "':
- nDelete = 1
- tInsert = self._typDQOpen
-
- elif self.mainConf.doReplaceDQuote and theOne == '"':
- nDelete = 1
- if thePos == 1:
- tInsert = self._typDQOpen
+ numA = 0
+ for n in range(fLen):
+ if self.document().characterAt(posE+n) == fChar:
+ numA += 1
else:
- tInsert = self._typDQClose
-
- elif self.mainConf.doReplaceSQuote and theTwo == " '":
- nDelete = 1
- tInsert = self._typSQOpen
-
- elif self.mainConf.doReplaceSQuote and theOne == "'":
- nDelete = 1
- if thePos == 1:
- tInsert = self._typSQOpen
- else:
- tInsert = self._typSQClose
-
- elif self.mainConf.doReplaceDash and theThree == "---":
- nDelete = 3
- tInsert = nwUnicode.U_EMDASH
-
- elif self.mainConf.doReplaceDash and theTwo == "--":
- nDelete = 2
- tInsert = nwUnicode.U_ENDASH
-
- elif self.mainConf.doReplaceDash and theTwo == nwUnicode.U_ENDASH + "-":
- nDelete = 2
- tInsert = nwUnicode.U_EMDASH
-
- elif self.mainConf.doReplaceDots and theThree == "...":
- nDelete = 3
- tInsert = nwUnicode.U_HELLIP
-
- tCheck = tInsert
- if tCheck in self.mainConf.fmtPadBefore:
- nDelete = max(nDelete, 1)
- tInsert = self._typPadChar + tInsert
-
- if tCheck in self.mainConf.fmtPadAfter:
- nDelete = max(nDelete, 1)
- tInsert = tInsert + self._typPadChar
-
- if nDelete > 0:
- theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, nDelete)
- theCursor.insertText(tInsert)
-
- return
-
- def _updateHeaders(self, checkPos=False, checkLevel=False):
- """Update the headers record and return True if anything
- changed, if a check flag was provided.
- """
- if self._docHandle is None:
- return False
-
- newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
- if checkPos:
- newPos = [x[0] for x in newHeaders]
- oldPos = [x[0] for x in self._docHeaders]
- if checkLevel:
- newLev = [x[1] for x in newHeaders]
- oldLev = [x[1] for x in self._docHeaders]
-
- self._docHeaders = newHeaders
-
- if checkPos:
- return newPos != oldPos
- if checkLevel:
- return newLev != oldLev
-
- return False
-
- def _replaceQuotes(self, sQuote, oQuote, cQuote):
- """Replace all straight quotes in the selected text.
- """
- theCursor = self.textCursor()
- if theCursor.hasSelection():
- posS = theCursor.selectionStart()
- posE = theCursor.selectionEnd()
- closeCheck = (
- " ", "\n", nwUnicode.U_LSEP, nwUnicode.U_PSEP
- )
-
- self._allowAutoReplace(False)
- for posC in range(posS, posE+1):
- theCursor.setPosition(posC)
- theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
- selText = theCursor.selectedText()
-
- nS = len(selText)
- if nS == 2:
- pC = selText[0]
- cC = selText[1]
- elif nS == 1:
- pC = " "
- cC = selText[0]
- else:
- continue
-
- if cC != sQuote:
- continue
-
- theCursor.clearSelection()
- theCursor.setPosition(posC)
- if pC in closeCheck:
- theCursor.beginEditBlock()
- theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
- theCursor.insertText(oQuote)
- theCursor.endEditBlock()
- else:
- theCursor.beginEditBlock()
- theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
- theCursor.insertText(cQuote)
- theCursor.endEditBlock()
-
- self._allowAutoReplace(True)
+ break
+ if fLen == min(numA, numB):
+ self._clearSurrounding(theCursor, fLen)
else:
- self.theParent.makeAlert(self.tr(
- "Please select some text before calling replace quotes."
- ), nwAlert.ERROR)
+ self._wrapSelection(fChar*fLen)
- return
+ return True
- def _checkDocSize(self, theSize):
- """Check if document size crosses the big document limit set in
- config. If so, we will set the big document flag to True.
+ def _clearSurrounding(self, theCursor, nChars):
+ """Clears n characters before and after the cursor.
"""
- bigLim = self.mainConf.bigDocLimit*1000
- newState = theSize > bigLim
+ if not theCursor.hasSelection():
+ logger.warning("No selection made, nothing to do")
+ return False
- if newState != self._bigDoc:
- if newState:
- logger.info(
- f"The document size is {theSize:n} > {bigLim:n}, "
- f"big doc mode has been enabled"
- )
- else:
- logger.info(
- f"The document size is {theSize:n} <= {bigLim:n}, "
- f"big doc mode has been disabled"
- )
+ posS = theCursor.selectionStart()
+ posE = theCursor.selectionEnd()
+ theCursor.clearSelection()
+ theCursor.beginEditBlock()
+ theCursor.setPosition(posS)
+ for i in range(nChars):
+ theCursor.deletePreviousChar()
+ theCursor.setPosition(posE)
+ for i in range(nChars):
+ theCursor.deletePreviousChar()
+ theCursor.endEditBlock()
+ theCursor.clearSelection()
- self._bigDoc = newState
-
- return
+ return True
def _wrapSelection(self, tBefore, tAfter=None):
"""Wraps the selected text in whatever is in tBefore and tAfter.
@@ -1593,120 +1447,84 @@ class GuiDocEditor(QTextEdit):
tAfter = tBefore
theCursor = self._autoSelect()
- if theCursor.hasSelection():
- posS = theCursor.selectionStart()
- posE = theCursor.selectionEnd()
-
- blockS = self._qDocument.findBlock(posS)
- blockE = self._qDocument.findBlock(posE)
- if blockS != blockE:
- posE = blockS.position() + blockS.length() - 1
-
- theCursor.clearSelection()
- theCursor.beginEditBlock()
- theCursor.setPosition(posE)
- theCursor.insertText(tAfter)
- theCursor.setPosition(posS)
- theCursor.insertText(tBefore)
- theCursor.endEditBlock()
-
- theCursor.setPosition(posE + len(tBefore), QTextCursor.MoveAnchor)
- theCursor.setPosition(posS + len(tBefore), QTextCursor.KeepAnchor)
- self.setTextCursor(theCursor)
-
- else:
+ if not theCursor.hasSelection():
logger.warning("No selection made, nothing to do")
- return
+ return False
- def _clearSurrounding(self, theCursor, nChars):
- """Clears n characters before and after the cursor.
- """
- if theCursor.hasSelection():
- posS = theCursor.selectionStart()
- posE = theCursor.selectionEnd()
- theCursor.clearSelection()
- theCursor.beginEditBlock()
- theCursor.setPosition(posS)
- for i in range(nChars):
- theCursor.deletePreviousChar()
- theCursor.setPosition(posE)
- for i in range(nChars):
- theCursor.deletePreviousChar()
- theCursor.endEditBlock()
- theCursor.clearSelection()
- else:
- logger.warning("No selection made, nothing to do")
- return
+ posS = theCursor.selectionStart()
+ posE = theCursor.selectionEnd()
- def _autoSelect(self):
- """Returns a cursor which may or may not have a selection based
- on user settings and document action.
+ qDoc = self.document()
+ blockS = qDoc.findBlock(posS)
+ blockE = qDoc.findBlock(posE)
+ if blockS != blockE:
+ posE = blockS.position() + blockS.length() - 1
+
+ theCursor.clearSelection()
+ theCursor.beginEditBlock()
+ theCursor.setPosition(posE)
+ theCursor.insertText(tAfter)
+ theCursor.setPosition(posS)
+ theCursor.insertText(tBefore)
+ theCursor.endEditBlock()
+
+ theCursor.setPosition(posE + len(tBefore), QTextCursor.MoveAnchor)
+ theCursor.setPosition(posS + len(tBefore), QTextCursor.KeepAnchor)
+ self.setTextCursor(theCursor)
+
+ return True
+
+ def _replaceQuotes(self, sQuote, oQuote, cQuote):
+ """Replace all straight quotes in the selected text.
"""
theCursor = self.textCursor()
- if self.mainConf.autoSelect and not theCursor.hasSelection():
- theCursor.select(QTextCursor.WordUnderCursor)
- posS = theCursor.selectionStart()
- posE = theCursor.selectionEnd()
+ if not theCursor.hasSelection():
+ self.theParent.makeAlert(self.tr(
+ "Please select some text before calling replace quotes."
+ ), nwAlert.ERROR)
+ return False
- # Underscore counts as a part of the word, so check that the
- # selection isn't wrapped in italics markers.
- reSelect = False
- if self._qDocument.characterAt(posS) == "_":
- posS += 1
- reSelect = True
- if self._qDocument.characterAt(posE) == "_":
- posE -= 1
- reSelect = True
- if reSelect:
- theCursor.clearSelection()
- theCursor.setPosition(posS, QTextCursor.MoveAnchor)
- theCursor.setPosition(posE-1, QTextCursor.KeepAnchor)
+ posS = theCursor.selectionStart()
+ posE = theCursor.selectionEnd()
+ closeCheck = (
+ " ", "\n", nwUnicode.U_LSEP, nwUnicode.U_PSEP
+ )
- self.setTextCursor(theCursor)
+ self._allowAutoReplace(False)
+ for posC in range(posS, posE+1):
+ theCursor.setPosition(posC)
+ theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
+ selText = theCursor.selectedText()
- return theCursor
+ nS = len(selText)
+ if nS == 2:
+ pC = selText[0]
+ cC = selText[1]
+ elif nS == 1:
+ pC = " "
+ cC = selText[0]
+ else: # pragma: no cover
+ continue
- def _toggleFormat(self, fLen, fChar):
- """Toggle the formatting of a specific type for a piece of text.
- If more than one block is selected, the formatting is applied to
- the first block.
- """
- theCursor = self._autoSelect()
- if theCursor.hasSelection():
- posS = theCursor.selectionStart()
- posE = theCursor.selectionEnd()
+ if cC != sQuote:
+ continue
- blockS = self._qDocument.findBlock(posS)
- blockE = self._qDocument.findBlock(posE)
-
- if blockS != blockE:
- posE = blockS.position() + blockS.length() - 1
- theCursor.clearSelection()
- theCursor.setPosition(posS, QTextCursor.MoveAnchor)
- theCursor.setPosition(posE, QTextCursor.KeepAnchor)
- self.setTextCursor(theCursor)
-
- numB = 0
- for n in range(fLen):
- if self._qDocument.characterAt(posS-n-1) == fChar:
- numB += 1
- else:
- break
-
- numA = 0
- for n in range(fLen):
- if self._qDocument.characterAt(posE+n) == fChar:
- numA += 1
- else:
- break
-
- cLevel = min(numB, numA)
- if cLevel == fLen:
- self._clearSurrounding(theCursor, fLen)
+ theCursor.clearSelection()
+ theCursor.setPosition(posC)
+ if pC in closeCheck:
+ theCursor.beginEditBlock()
+ theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
+ theCursor.insertText(oQuote)
+ theCursor.endEditBlock()
else:
- self._wrapSelection(fChar*fLen)
+ theCursor.beginEditBlock()
+ theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
+ theCursor.insertText(cQuote)
+ theCursor.endEditBlock()
- return
+ self._allowAutoReplace(True)
+
+ return True
def _formatBlock(self, docAction):
"""Changes the block format of the block under the cursor.
@@ -1874,6 +1692,206 @@ class GuiDocEditor(QTextEdit):
return True
+ ##
+ # Internal Functions
+ ##
+
+ def _followTag(self, theCursor=None, loadTag=True):
+ """Activated by Ctrl+Enter. Checks that we're in a block
+ starting with '@'. We then find the word under the cursor and
+ check that it is after the ':'. If all this is fine, we have a
+ tag and can tell the document viewer to try and find and load
+ the file where the tag is defined.
+ """
+ if theCursor is None:
+ theCursor = self.textCursor()
+
+ theBlock = theCursor.block()
+ theText = theBlock.text()
+
+ if len(theText) == 0:
+ return False
+
+ if theText.startswith("@"):
+
+ theCursor.select(QTextCursor.WordUnderCursor)
+ theWord = theCursor.selectedText()
+ cPos = theText.find(":")
+ wPos = theCursor.selectionStart() - theBlock.position()
+ if wPos <= cPos:
+ return False
+
+ if loadTag:
+ logger.verbose("Attempting to follow tag '%s'" % theWord)
+ self.theParent.docViewer.loadFromTag(theWord)
+ else:
+ logger.verbose("Potential tag '%s'" % theWord)
+
+ return True
+
+ return False
+
+ def _openSpellContext(self):
+ """Opens the spell check context menu at the current point of
+ the cursor.
+ """
+ self._openContextMenu(self.cursorRect().center())
+ return
+
+ def _docAutoReplace(self, theBlock):
+ """Auto-replace text elements based on main configuration.
+ """
+ if not theBlock.isValid():
+ return
+
+ theText = theBlock.text()
+ theCursor = self.textCursor()
+ thePos = theCursor.positionInBlock()
+ theLen = len(theText)
+
+ if theLen < 1 or thePos-1 > theLen:
+ return
+
+ theOne = theText[thePos-1:thePos]
+ theTwo = theText[thePos-2:thePos]
+ theThree = theText[thePos-3:thePos]
+
+ if not theOne:
+ # Sorry, Neo and Zathras
+ return
+
+ nDelete = 0
+ tInsert = theOne
+
+ if self.mainConf.doReplaceDQuote and theTwo == ' "':
+ nDelete = 1
+ tInsert = self._typDQOpen
+
+ elif self.mainConf.doReplaceDQuote and theOne == '"':
+ nDelete = 1
+ if thePos == 1:
+ tInsert = self._typDQOpen
+ else:
+ tInsert = self._typDQClose
+
+ elif self.mainConf.doReplaceSQuote and theTwo == " '":
+ nDelete = 1
+ tInsert = self._typSQOpen
+
+ elif self.mainConf.doReplaceSQuote and theOne == "'":
+ nDelete = 1
+ if thePos == 1:
+ tInsert = self._typSQOpen
+ else:
+ tInsert = self._typSQClose
+
+ elif self.mainConf.doReplaceDash and theThree == "---":
+ nDelete = 3
+ tInsert = nwUnicode.U_EMDASH
+
+ elif self.mainConf.doReplaceDash and theTwo == "--":
+ nDelete = 2
+ tInsert = nwUnicode.U_ENDASH
+
+ elif self.mainConf.doReplaceDash and theTwo == nwUnicode.U_ENDASH + "-":
+ nDelete = 2
+ tInsert = nwUnicode.U_EMDASH
+
+ elif self.mainConf.doReplaceDots and theThree == "...":
+ nDelete = 3
+ tInsert = nwUnicode.U_HELLIP
+
+ tCheck = tInsert
+ if tCheck in self.mainConf.fmtPadBefore:
+ nDelete = max(nDelete, 1)
+ tInsert = self._typPadChar + tInsert
+
+ if tCheck in self.mainConf.fmtPadAfter:
+ nDelete = max(nDelete, 1)
+ tInsert = tInsert + self._typPadChar
+
+ if nDelete > 0:
+ theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, nDelete)
+ theCursor.insertText(tInsert)
+
+ return
+
+ def _updateHeaders(self, checkPos=False, checkLevel=False):
+ """Update the headers record and return True if anything
+ changed, if a check flag was provided.
+ """
+ if self._docHandle is None:
+ return False
+
+ newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
+ if checkPos:
+ newPos = [x[0] for x in newHeaders]
+ oldPos = [x[0] for x in self._docHeaders]
+ if checkLevel:
+ newLev = [x[1] for x in newHeaders]
+ oldLev = [x[1] for x in self._docHeaders]
+
+ self._docHeaders = newHeaders
+
+ if checkPos:
+ return newPos != oldPos
+ if checkLevel:
+ return newLev != oldLev
+
+ return False
+
+ def _checkDocSize(self, theSize):
+ """Check if document size crosses the big document limit set in
+ config. If so, we will set the big document flag to True.
+ """
+ bigLim = round(self.mainConf.bigDocLimit*1000)
+ newState = theSize > bigLim
+
+ if newState != self._bigDoc:
+ if newState:
+ logger.info(
+ f"The document size is {theSize:n} > {bigLim:n}, "
+ f"big doc mode has been enabled"
+ )
+ else:
+ logger.info(
+ f"The document size is {theSize:n} <= {bigLim:n}, "
+ f"big doc mode has been disabled"
+ )
+
+ self._bigDoc = newState
+
+ return
+
+ def _autoSelect(self):
+ """Returns a cursor which may or may not have a selection based
+ on user settings and document action.
+ """
+ theCursor = self.textCursor()
+ if self.mainConf.autoSelect and not theCursor.hasSelection():
+ theCursor.select(QTextCursor.WordUnderCursor)
+ posS = theCursor.selectionStart()
+ posE = theCursor.selectionEnd()
+
+ # Underscore counts as a part of the word, so check that the
+ # selection isn't wrapped in italics markers.
+ reSelect = False
+ qDoc = self.document()
+ if qDoc.characterAt(posS) == "_":
+ posS += 1
+ reSelect = True
+ if qDoc.characterAt(posE) == "_":
+ posE -= 1
+ reSelect = True
+ if reSelect:
+ theCursor.clearSelection()
+ theCursor.setPosition(posS, QTextCursor.MoveAnchor)
+ theCursor.setPosition(posE-1, QTextCursor.KeepAnchor)
+
+ self.setTextCursor(theCursor)
+
+ return theCursor
+
def _makeSelection(self, selMode):
"""Wrapper function to select text based on a selection mode.
"""
@@ -2753,7 +2771,7 @@ class GuiDocEditFooter(QWidget):
else:
theCursor = self.docEditor.textCursor()
iLine = theCursor.blockNumber() + 1
- iDist = 100*iLine/self.docEditor._qDocument.blockCount()
+ iDist = 100*iLine/self.docEditor.document().blockCount()
self.linesText.setText(
self.tr("Line: {0} ({1})").format(f"{iLine:n}", f"{iDist:.0f} %")
@@ -2775,7 +2793,7 @@ class GuiDocEditFooter(QWidget):
self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}")
)
- byteSize = self.docEditor._qDocument.characterCount()
+ byteSize = self.docEditor.document().characterCount()
self.wordsText.setToolTip(
self.tr("Document size is {0} bytes").format(f"{byteSize:n}")
)
diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py
index 4937f0bd..628f9511 100644
--- a/nw/gui/dochighlight.py
+++ b/nw/gui/dochighlight.py
@@ -261,11 +261,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"""Loop through all blocks and rehighlight those of a given
content type.
"""
- qDocument = self.document()
- nBlocks = qDocument.blockCount()
+ qDoc = self.document()
+ nBlocks = qDoc.blockCount()
bfTime = time()
for i in range(nBlocks):
- theBlock = qDocument.findBlockByNumber(i)
+ theBlock = qDoc.findBlockByNumber(i)
if theBlock.userState() & theType > 0:
self.rehighlightBlock(theBlock)
afTime = time()
diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py
index 86e137fe..ddfe9b32 100644
--- a/nw/gui/docviewer.py
+++ b/nw/gui/docviewer.py
@@ -61,7 +61,6 @@ class GuiDocViewer(QTextBrowser):
# Internal Variables
self._docHandle = None
- self._qDocument = self.document()
# Settings
self.setMinimumWidth(self.mainConf.pxInt(300))
@@ -106,7 +105,7 @@ class GuiDocViewer(QTextBrowser):
theFont = QFont()
if self.mainConf.textFont is None:
# If none is defined, set the default back to config
- self.mainConf.textFont = self._qDocument.defaultFont().family()
+ self.mainConf.textFont = self.document().defaultFont().family()
theFont.setFamily(self.mainConf.textFont)
theFont.setPointSize(self.mainConf.textSize)
self.setFont(theFont)
@@ -127,11 +126,11 @@ class GuiDocViewer(QTextBrowser):
self.docFooter.matchColours()
# Set default text margins
- self._qDocument.setDocumentMargin(0)
+ self.document().setDocumentMargin(0)
theOpt = QTextOption()
if self.mainConf.doJustify:
theOpt.setAlignment(Qt.AlignJustify)
- self._qDocument.setDefaultTextOption(theOpt)
+ self.document().setDefaultTextOption(theOpt)
# Scroll bars
if self.mainConf.hideVScroll:
@@ -234,7 +233,7 @@ class GuiDocViewer(QTextBrowser):
def redrawText(self):
"""Redraw the text by marking the document content as "dirty".
"""
- self._qDocument.markContentsDirty(0, self._qDocument.characterCount())
+ self.document().markContentsDirty(0, self.document().characterCount())
self.updateDocMargins()
return
@@ -373,7 +372,7 @@ class GuiDocViewer(QTextBrowser):
if not isinstance(theLine, int):
return False
if theLine >= 0:
- theBlock = self._qDocument.findBlockByLineNumber(theLine)
+ theBlock = self.document().findBlockByLineNumber(theLine)
if theBlock:
self.setCursorPosition(theBlock.position())
logger.verbose("Cursor moved to line %d", theLine)
@@ -563,7 +562,7 @@ class GuiDocViewer(QTextBrowser):
mColG=self.theTheme.colMod[1],
mColB=self.theTheme.colMod[2],
)
- self._qDocument.setDefaultStyleSheet(styleSheet)
+ self.document().setDefaultStyleSheet(styleSheet)
return True
diff --git a/nw/tools/build.py b/nw/tools/build.py
index 4e15b6d3..3dba8901 100644
--- a/nw/tools/build.py
+++ b/nw/tools/build.py
@@ -980,7 +980,7 @@ class GuiBuildNovel(QDialog):
thePrinter.setFontEmbeddingEnabled(True)
thePrinter.setColorMode(QPrinter.Color)
thePrinter.setOutputFileName(savePath)
- self.docView.qDocument.print(thePrinter)
+ self.docView.document().print(thePrinter)
wSuccess = True
except Exception as e:
@@ -1018,7 +1018,7 @@ class GuiBuildNovel(QDialog):
"""
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
thePrinter.setOrientation(QPrinter.Portrait)
- self.docView.qDocument.print(thePrinter)
+ self.docView.document().print(thePrinter)
qApp.restoreOverrideCursor()
return
@@ -1195,8 +1195,7 @@ class GuiBuildNovelDocView(QTextBrowser):
self.setMinimumWidth(40*self.theParent.theTheme.textNWidth)
self.setOpenExternalLinks(False)
- self.qDocument = self.document()
- self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
+ self.document().setDocumentMargin(self.mainConf.getTextMargin())
self.setPlaceholderText(self.tr(
"This area will show the content of the document to be "
"exported or printed. Press the \"Build Preview\" button "
@@ -1206,7 +1205,7 @@ class GuiBuildNovelDocView(QTextBrowser):
theFont = QFont()
if self.mainConf.textFont is None:
# If none is defined, set the default back to config
- self.mainConf.textFont = self.qDocument.defaultFont().family()
+ self.mainConf.textFont = self.document().defaultFont().family()
theFont.setFamily(self.mainConf.textFont)
theFont.setPointSize(self.mainConf.textSize)
self.setFont(theFont)
@@ -1256,12 +1255,12 @@ class GuiBuildNovelDocView(QTextBrowser):
def setJustify(self, doJustify):
"""Set the justify text option.
"""
- theOpt = self.qDocument.defaultTextOption()
+ theOpt = self.document().defaultTextOption()
if doJustify:
theOpt.setAlignment(Qt.AlignJustify)
else:
theOpt.setAlignment(Qt.AlignAbsolute)
- self.qDocument.setDefaultTextOption(theOpt)
+ self.document().setDefaultTextOption(theOpt)
return
def setTextFont(self, textFont, textSize):
@@ -1298,7 +1297,7 @@ class GuiBuildNovelDocView(QTextBrowser):
# Since we change the content while it may still be rendering, we mark
# the document dirty again to make sure it's re-rendered properly.
- self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
+ self.document().markContentsDirty(0, self.document().characterCount())
qApp.restoreOverrideCursor()
return
@@ -1312,14 +1311,14 @@ class GuiBuildNovelDocView(QTextBrowser):
theStyles.append("a {color: rgb(66, 113, 174);}")
theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
- self.qDocument.setDefaultStyleSheet("\n".join(theStyles))
+ self.document().setDefaultStyleSheet("\n".join(theStyles))
return
def clearStyleSheet(self):
"""Clears the document stylesheet.
"""
- self.qDocument.setDefaultStyleSheet("")
+ self.document().setDefaultStyleSheet("")
return
##
diff --git a/tests/conftest.py b/tests/conftest.py
index dbe5f318..93ed72a1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -240,3 +240,62 @@ def nwOldProj(tmpDir):
shutil.rmtree(dstDir)
return
+
+
+##
+# Useful Fixtures
+##
+
+@pytest.fixture(scope="session")
+def ipsumText():
+ """Return five paragraphs of Lorem Ipsum text.
+ """
+ thatIpsum = [(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc maximus justo non dictum co"
+ "mmodo. Curabitur lacinia tempor orci vel luctus. Phasellus porta metus eu massa luctus, e"
+ "get euismod risus rhoncus. Vestibulum sed arcu nisi. Maecenas pretium facilisis velit, ve"
+ "l semper lacus aliquam sit amet. Vestibulum vulputate neque ligula, rhoncus blandit turpi"
+ "s consequat id. Mauris sagittis vehicula imperdiet. Duis sed nunc pretium, ornare purus v"
+ "el, sodales augue. Maecenas a suscipit risus. Quisque volutpat justo eleifend est ullamco"
+ "rper fermentum. Donec ullamcorper et tortor a laoreet. Nam id risus nisi. Vivamus non imp"
+ "erdiet erat, sit amet imperdiet felis. Mauris vitae neque et est aliquam scelerisque non "
+ "non ipsum."
+ ), (
+ "Nullam laoreet lorem nec malesuada vehicula. Vivamus tempus sodales lectus sed viverra. A"
+ "enean lacinia sollicitudin quam, quis tempus eros suscipit id. Duis sed rutrum nisi, ut p"
+ "ulvinar magna. Nam et cursus tortor. Phasellus ac odio tellus. Nullam in iaculis ipsum. V"
+ "ivamus ante sem, ultricies sed varius quis, tristique nec tellus. Nullam eu urna vitae la"
+ "cus hendrerit gravida. Quisque pulvinar erat ex, id efficitur velit sodales vitae. Proin "
+ "vestibulum, sapien eget mattis euismod, tortor quam viverra risus, at congue mauris torto"
+ "r eu nunc. Mauris pellentesque elit leo, quis eleifend sem placerat a. Vivamus iaculis du"
+ "i eget tellus volutpat, ac varius nisi facilisis."
+ ), (
+ "Nullam a nisl magna. Praesent commodo nec diam aliquet vestibulum. In sapien velit, sodal"
+ "es feugiat porta ut, rhoncus a elit. Quisque egestas nisi eu eros laoreet, quis facilisis"
+ " est pretium. Nullam bibendum sed tellus nec lobortis. Duis elit massa, volutpat a lacini"
+ "a a, ullamcorper in dui. Suspendisse ac laoreet dui. Curabitur elementum, tortor elementu"
+ "m ultricies laoreet, nunc massa vulputate augue, vitae tincidunt nunc enim eget nisl."
+ ), (
+ "Pellentesque nibh urna, volutpat et feugiat porta, rutrum sed lectus. Aliquam eget risus "
+ "id orci tincidunt condimentum et sit amet purus. Curabitur tincidunt odio vel ante feugia"
+ "t feugiat. Proin nunc lorem, molestie a sapien et, varius elementum nunc. Donec non ferme"
+ "ntum nisl. In et massa placerat, faucibus felis eu, congue nisi. Proin sed tortor non lor"
+ "em mattis cursus. Vestibulum magna neque, bibendum vel nibh et, tincidunt rhoncus nisi. D"
+ "uis pulvinar mi a quam rutrum maximus. Nunc sollicitudin, urna in cursus facilisis, augue"
+ " neque imperdiet metus, ac finibus lorem ante id nulla. Sed maximus eleifend justo id feu"
+ "giat. Cras eget diam vel est blandit tempor nec a leo. Mauris risus est, fringilla in ali"
+ "quam a, sagittis vel enim. Nullam sodales id erat placerat lobortis."
+ ), (
+ "Integer ac gravida quam. Quisque eleifend nisl nec pretium tincidunt. Quisque sollicitudi"
+ "n nisi in hendrerit scelerisque. Sed ornare nisl lacus, sit amet consectetur lectus egest"
+ "as et. Vivamus nec arcu lorem. Donec rhoncus, purus a porta accumsan, nunc lectus iaculis"
+ " libero, et fringilla tellus augue et velit. Integer varius felis scelerisque, vulputate "
+ "tellus eu, laoreet justo. Suspendisse sit amet sem vehicula, auctor odio sed, aliquet eni"
+ "m. In ac tortor sed tortor fringilla elementum. Nulla non odio at magna vulputate sceleri"
+ "sque. Nam elementum diam eu rutrum scelerisque. Sed fermentum, felis quis vulputate ferme"
+ "ntum, libero metus sollicitudin est, in faucibus purus nulla non dolor. Ut vitae felis po"
+ "rta, feugiat nunc et, bibendum neque. Nullam nec lorem nec metus ullamcorper malesuada ut"
+ " a nisl. Etiam eget tristique dui. Nulla sed mi finibus, venenatis tellus non, maximus en"
+ "im."
+ )]
+ return thatIpsum
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index aee859a3..75b734eb 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -19,20 +19,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import os
import pytest
-from shutil import copyfile
-from tools import cmpFiles
+from mock import causeOSError
from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QTextCursor
-from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
+from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
+from PyQt5.QtWidgets import QAction, QMessageBox, qApp
-from nw.dialogs.itemeditor import GuiItemEditor
from nw.gui.doceditor import GuiDocEditor
-from nw.gui.projtree import GuiProjectTree
-from nw.enum import nwItemType, nwDocAction, nwWidget
+from nw.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout
+from nw.constants import nwKeyWords, nwUnicode
keyDelay = 2
typeDelay = 1
@@ -40,318 +37,1127 @@ stepDelay = 20
@pytest.mark.gui
-def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
- """Test the document editor.
+def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
+ """Test initialising the editor.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
- monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
- monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
- monkeypatch.setattr(GuiItemEditor, "result", lambda *a: QDialog.Accepted)
- monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
- monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
- # Create new, save, close project
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": fncProj})
- assert nwGUI.saveProject()
- assert nwGUI.closeProject()
+ # Open project
+ assert nwGUI.openProject(nwMinimal)
+ assert nwGUI.openDocument("8c659a11cd429")
- assert len(nwGUI.theProject.projTree) == 0
- assert len(nwGUI.theProject.projTree._treeOrder) == 0
- assert len(nwGUI.theProject.projTree._treeRoots) == 0
- assert nwGUI.theProject.projTree.trashRoot() is None
- assert nwGUI.theProject.projPath is None
- assert nwGUI.theProject.projMeta is None
- assert nwGUI.theProject.projFile == "nwProject.nwx"
- assert nwGUI.theProject.projName == ""
- assert nwGUI.theProject.bookTitle == ""
- assert len(nwGUI.theProject.bookAuthors) == 0
- assert not nwGUI.theProject.spellCheck
-
- # Check the files
- projFile = os.path.join(fncProj, "nwProject.nwx")
- testFile = os.path.join(outDir, "guiEditor_Main_Initial_nwProject.nwx")
- compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
- qtbot.wait(stepDelay)
-
- # qtbot.stopForInteraction()
-
- # Re-open project
- assert nwGUI.openProject(fncProj)
- qtbot.wait(stepDelay)
-
- # Check that we loaded the data
- assert len(nwGUI.theProject.projTree) == 8
- 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 == fncProj
- assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
- assert nwGUI.theProject.projFile == "nwProject.nwx"
- assert nwGUI.theProject.projName == "New Project"
- assert nwGUI.theProject.bookTitle == ""
- assert len(nwGUI.theProject.bookAuthors) == 0
- assert not nwGUI.theProject.spellCheck
-
- # Check that tree items have been created
- assert nwGUI.treeView._getTreeItem("73475cb40a568") is not None
- assert nwGUI.treeView._getTreeItem("25fc0e7096fc6") is not None
- assert nwGUI.treeView._getTreeItem("31489056e0916") is not None
- assert nwGUI.treeView._getTreeItem("98010bd9270f9") is not None
- assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
- assert nwGUI.treeView._getTreeItem("44cb730c42048") is not None
- assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
- assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
-
- nwGUI.mainMenu.aSpellCheck.setChecked(True)
- assert nwGUI.mainMenu._toggleSpellCheck()
-
- # Change some settings
- nwGUI.mainConf.hideHScroll = True
- nwGUI.mainConf.hideVScroll = True
- nwGUI.mainConf.scrollPastEnd = True
- nwGUI.mainConf.autoScrollPos = 80
- nwGUI.mainConf.autoScroll = True
-
- # Add a Character File
- nwGUI.switchFocus(nwWidget.TREE)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
-
- # Type something into the document
- nwGUI.switchFocus(nwWidget.EDITOR)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Jane Doe":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@tag: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "This is a file about Jane.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- # Add a Plot File
- nwGUI.switchFocus(nwWidget.TREE)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
-
- # Type something into the document
- nwGUI.switchFocus(nwWidget.EDITOR)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Main Plot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@tag: MainPlot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "This is a file detailing the main plot.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- # Add a World File
- nwGUI.switchFocus(nwWidget.TREE)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
-
- # Add Some Text
- nwGUI.docEditor.replaceText("Hello World!")
- assert nwGUI.docEditor.getText() == "Hello World!"
- nwGUI.docEditor.replaceText("")
-
- # Type something into the document
- nwGUI.switchFocus(nwWidget.EDITOR)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Main Location":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@tag: Home":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "This is a file describing Jane's home.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- # Trigger autosaves before making more changes
- nwGUI._autoSaveDocument()
- nwGUI._autoSaveProject()
-
- # Select the 'New Scene' file
- nwGUI.switchFocus(nwWidget.TREE)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
- nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True)
- nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
- assert nwGUI.openSelectedItem()
-
- # Type something into the document
- nwGUI.switchFocus(nwWidget.EDITOR)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Novel":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "## Chapter":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "@pov: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@plot: MainPlot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "### Scene":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "% How about a comment?":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@pov: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@plot: MainPlot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@location: Home":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "#### Some Section":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "@char: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "This is a paragraph of nonsense text.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in (
- "This is another paragraph of much longer nonsense text. "
- "It is in fact 1 very very NONSENSICAL nonsense text! "
- ):
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "Isn't that nice? ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "Ellipsis? Not a problem either ... ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "How about three hyphens - -":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay)
- for c in "- for long dash? It works too.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "\"Full line double quoted text.\"":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "'Full line single quoted text.'":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- qtbot.wait(stepDelay)
- nwGUI.docEditor.wCounter.run()
- qtbot.wait(stepDelay)
-
- # Save the document
- assert nwGUI.docEditor.docChanged()
+ nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0])
assert nwGUI.saveDocument()
- assert not nwGUI.docEditor.docChanged()
- qtbot.wait(stepDelay)
- nwGUI.rebuildIndex()
qtbot.wait(stepDelay)
- # Open and view the edited document
- nwGUI.switchFocus(nwWidget.VIEWER)
- assert nwGUI.openDocument("0e17daca5f3e1")
- assert nwGUI.viewDocument("0e17daca5f3e1")
- qtbot.wait(stepDelay)
- assert nwGUI.saveProject()
- assert nwGUI.closeDocViewer()
- qtbot.wait(stepDelay)
+ # Check Defaults
+ qDoc = nwGUI.docEditor.document()
+ assert qDoc.defaultTextOption().alignment() == Qt.AlignLeft
+ assert nwGUI.docEditor.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
+ assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
+ assert nwGUI.docEditor._typPadChar == nwUnicode.U_NBSP
- # Check a Quick Create and Delete
- assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- newHandle = nwGUI.treeView.getSelectedHandle()
- assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
- assert nwGUI.treeView.deleteItem()
- assert nwGUI.treeView.setSelectedHandle(newHandle)
- assert nwGUI.treeView.deleteItem()
- assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
- assert nwGUI.saveProject()
+ # Check that editor handles settings
+ nwGUI.mainConf.textFont = None
+ nwGUI.mainConf.doJustify = True
+ nwGUI.mainConf.showTabsNSpaces = True
+ nwGUI.mainConf.showLineEndings = True
+ nwGUI.mainConf.hideVScroll = True
+ nwGUI.mainConf.hideHScroll = True
+ nwGUI.mainConf.fmtPadThin = True
- # Check the files
- projFile = os.path.join(fncProj, "nwProject.nwx")
- testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
- compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+ assert nwGUI.docEditor.initEditor()
- projFile = os.path.join(fncProj, "content", "031b4af5197ec.nwd")
- testFile = os.path.join(outDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
- compFile = os.path.join(refDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile)
-
- projFile = os.path.join(fncProj, "content", "1a6562590ef19.nwd")
- testFile = os.path.join(outDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
- compFile = os.path.join(refDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile)
-
- projFile = os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
- testFile = os.path.join(outDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
- compFile = os.path.join(refDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile)
-
- projFile = os.path.join(fncProj, "content", "41cfc0d1f2d12.nwd")
- testFile = os.path.join(outDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
- compFile = os.path.join(refDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile)
+ qDoc = nwGUI.docEditor.document()
+ assert nwGUI.mainConf.textFont == qDoc.defaultFont().family()
+ assert qDoc.defaultTextOption().alignment() == Qt.AlignJustify
+ assert qDoc.defaultTextOption().flags() & QTextOption.ShowTabsAndSpaces
+ assert qDoc.defaultTextOption().flags() & QTextOption.ShowLineAndParagraphSeparators
+ assert nwGUI.docEditor.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
+ assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
+ assert nwGUI.docEditor._typPadChar == nwUnicode.U_THNBSP
# qtbot.stopForInteraction()
-# END Test testGuiEditor_Main
+# END Test testGuiEditor_Init
+
+
+@pytest.mark.gui
+def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText):
+ """Test loading text into the editor.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+
+ longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20)
+ nwGUI.docEditor.replaceText(longText)
+ assert nwGUI.saveDocument() is True
+ assert nwGUI.closeDocument() is True
+ qtbot.wait(stepDelay)
+
+ # Load Text
+ # =========
+
+ # Invalid handle
+ assert nwGUI.docEditor.loadText("abcdefghijklm") is False
+
+ # Document too big
+ with monkeypatch.context() as mp:
+ mp.setattr("nw.constants.nwConst.MAX_DOCSIZE", 100)
+ assert nwGUI.docEditor.loadText(sHandle) is False
+ assert "The document you are trying to open is too big." in caplog.text
+
+ # Regular open
+ assert nwGUI.docEditor.loadText(sHandle) is True
+ assert nwGUI.docEditor._bigDoc is False
+
+ # Reload too big text
+ with monkeypatch.context() as mp:
+ mp.setattr("nw.constants.nwConst.MAX_DOCSIZE", 100)
+ assert nwGUI.docEditor.replaceText(longText) is False
+ assert "The document you are trying to open is too big." in caplog.text
+
+ # Big doc handling
+ nwGUI.mainConf.bigDocLimit = 50
+ assert nwGUI.docEditor.loadText(sHandle) is True
+ assert nwGUI.docEditor._bigDoc is True
+
+ # Regular open, with line number
+ assert nwGUI.docEditor.loadText(sHandle, tLine=4) is True
+ cursPos = nwGUI.docEditor.getCursorPosition()
+ assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4
+
+ # Load empty document
+ nwGUI.docEditor.replaceText("")
+ assert nwGUI.saveDocument() is True
+ assert nwGUI.docEditor.loadText(sHandle) is True
+ assert nwGUI.docEditor.toPlainText() == ""
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_LoadText
+
+
+@pytest.mark.gui
+def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText):
+ """Test saving text from the editor.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ # Save Text
+ # =========
+
+ longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText)
+ nwGUI.docEditor.replaceText(longText)
+
+ # Missing item
+ nwItem = nwGUI.docEditor._nwItem
+ nwGUI.docEditor._nwItem = None
+ assert nwGUI.docEditor.saveText() is False
+ nwGUI.docEditor._nwItem = nwItem
+
+ # Unkown handle
+ nwGUI.docEditor._docHandle = "0123456789abcdef"
+ assert nwGUI.docEditor.saveText() is False
+ nwGUI.docEditor._docHandle = sHandle
+
+ # Cause error when saving
+ with monkeypatch.context() as mp:
+ mp.setattr("builtins.open", causeOSError)
+ assert nwGUI.docEditor.saveText() is False
+ assert "Could not save document." in caplog.text
+
+ # Change header level
+ assert nwGUI.theProject.projTree[sHandle].itemLayout == nwItemLayout.SCENE
+ nwGUI.docEditor.replaceText(longText[1:])
+ assert nwGUI.docEditor.saveText() is True
+ assert nwGUI.theProject.projTree[sHandle].itemLayout == nwItemLayout.CHAPTER
+
+ # Regular save
+ assert nwGUI.docEditor.saveText() is True
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_SaveText
+
+
+@pytest.mark.gui
+def testGuiEditor_MetaData(qtbot, monkeypatch, nwGUI, nwMinimal):
+ """Test extracting various meta data and other values.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ # Get Text
+ # Both methods should return the same result for line breaks, but not for spaces
+ newText = (
+ "### New Scene\u2029\u2029"
+ "Some\u2028text.\u2029"
+ "More\u00a0text.\u2029"
+ )
+ assert nwGUI.docEditor.replaceText(newText)
+ assert nwGUI.docEditor.getText() == "### New Scene\n\nSome\ntext.\nMore\u00a0text.\n"
+ verQtValue = nwGUI.mainConf.verQtValue
+ nwGUI.mainConf.verQtValue = 50800
+ assert nwGUI.docEditor.getText() == "### New Scene\n\nSome\ntext.\nMore text.\n"
+ nwGUI.mainConf.verQtValue = verQtValue
+
+ # Check Propertoes
+ assert nwGUI.docEditor.docChanged() is True
+ assert nwGUI.docEditor.docHandle() == sHandle
+ assert nwGUI.docEditor.lastActive() > 0.0
+ assert nwGUI.docEditor.isEmpty() is False
+ assert nwGUI.docEditor.currentDictionary() is not None
+
+ # Cursor Position
+ assert nwGUI.docEditor.setCursorPosition(None) is False
+ assert nwGUI.docEditor.setCursorPosition(10) is True
+ assert nwGUI.docEditor.getCursorPosition() == 10
+ assert nwGUI.theProject.projTree[sHandle].cursorPos != 10
+ nwGUI.docEditor.saveCursorPosition()
+ assert nwGUI.theProject.projTree[sHandle].cursorPos == 10
+
+ assert nwGUI.docEditor.setCursorLine(None) is False
+ assert nwGUI.docEditor.setCursorLine(2) is True
+ assert nwGUI.docEditor.getCursorPosition() == 15
+
+ # Document Changed Signal
+ nwGUI.docEditor._docChanged = False
+ with qtbot.waitSignal(nwGUI.docEditor.docEditedStatusChanged, raising=True, timeout=100):
+ nwGUI.docEditor.setDocumentChanged(True)
+ assert nwGUI.docEditor._docChanged is True
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_MetaData
+
+
+@pytest.mark.gui
+def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
+ """Test the document actions. This is not an extensive test of the
+ action features, just that the actions are actually called. The
+ various action features are tested when their respective functions
+ are tested.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ theDoc = nwGUI.docEditor.document()
+
+ # Select/Cut/Copy/Paste/Undo/Redo
+ # ===============================
+
+ qApp.clipboard().clear()
+
+ # Select All
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.hasSelection() is True
+ assert theCursor.selectedText() == theText.replace("\n", "\u2029")
+ theCursor.clearSelection()
+
+ # Select Paragraph
+ assert nwGUI.docEditor.setCursorPosition(1000) is True
+ assert nwGUI.docEditor.getCursorPosition() == 1000
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == ipsumText[1]
+
+ # Cut Selected Text
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(1000) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.CUT) is True
+
+ newText = nwGUI.docEditor.getText()
+ newPara = list(filter(str.strip, newText.split("\n")))
+ assert newPara[0] == "### A Scene"
+ assert newPara[1] == ipsumText[0]
+ assert newPara[2] == ipsumText[2]
+ assert newPara[3] == ipsumText[3]
+ assert newPara[4] == ipsumText[4]
+
+ # Paste Back In
+ assert nwGUI.docEditor.docAction(nwDocAction.PASTE) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Copy Next Paragraph
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(1500) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.COPY) is True
+
+ # Paste at End
+ assert nwGUI.docEditor.setCursorPosition(theDoc.characterCount()) is True
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.insertBlock()
+ theCursor.insertBlock()
+
+ assert nwGUI.docEditor.docAction(nwDocAction.PASTE) is True
+ newText = nwGUI.docEditor.getText()
+ newPara = list(filter(str.strip, newText.split("\n")))
+ assert newPara[5] == ipsumText[4]
+ assert newPara[6] == ipsumText[2]
+
+ qApp.clipboard().clear()
+
+ # Emphasis/Undo/Redo
+ # ==================
+
+ theText = "### A Scene\n\n%s" % ipsumText[0]
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Emphasis
+ assert nwGUI.docEditor.setCursorPosition(50) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.EMPH) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "_consectetur_")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Strong
+ assert nwGUI.docEditor.setCursorPosition(50) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.STRONG) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "**consectetur**")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Strikeout
+ assert nwGUI.docEditor.setCursorPosition(50) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.STRIKE) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "~~consectetur~~")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Redo
+ assert nwGUI.docEditor.docAction(nwDocAction.REDO) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "~~consectetur~~")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Quotes
+ # ======
+
+ theText = "### A Scene\n\n%s" % ipsumText[0]
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Add Single Quotes
+ assert nwGUI.docEditor.setCursorPosition(50) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.S_QUOTE) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Add Double Quotes
+ assert nwGUI.docEditor.setCursorPosition(50) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.D_QUOTE) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Replace Single Quotes
+ repText = theText.replace("consectetur", "'consectetur'")
+ assert nwGUI.docEditor.replaceText(repText) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.REPL_SNG) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019")
+
+ # Replace Double Quotes
+ repText = theText.replace("consectetur", "\"consectetur\"")
+ assert nwGUI.docEditor.replaceText(repText) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.REPL_DBL) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d")
+
+ # Remove Line Breaks
+ # ==================
+
+ theText = "### A Scene\n\n%s" % ipsumText[0]
+ repText = theText[:100] + theText[100:].replace(" ", "\n", 3)
+ assert nwGUI.docEditor.replaceText(repText) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.RM_BREAKS) is True
+ assert nwGUI.docEditor.getText().strip() == theText.strip()
+
+ # Format Block
+ # ============
+
+ theText = "## Scene Title\n\nScene text.\n\n"
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Header 1
+ assert nwGUI.docEditor.setCursorPosition(0) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_H1) is True
+ assert nwGUI.docEditor.getText() == "# Scene Title\n\nScene text.\n\n"
+
+ # Header 2
+ assert nwGUI.docEditor.setCursorPosition(0) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_H2) is True
+ assert nwGUI.docEditor.getText() == "## Scene Title\n\nScene text.\n\n"
+
+ # Header 3
+ assert nwGUI.docEditor.setCursorPosition(0) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_H3) is True
+ assert nwGUI.docEditor.getText() == "### Scene Title\n\nScene text.\n\n"
+
+ # Header 4
+ assert nwGUI.docEditor.setCursorPosition(0) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_H4) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\nScene text.\n\n"
+
+ # Comment
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_COM) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\n% Scene text.\n\n"
+
+ # Text
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\nScene text.\n\n"
+
+ # Align Left
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.ALIGN_L) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\nScene text. <<\n\n"
+
+ # Align Right
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.ALIGN_R) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\n>> Scene text.\n\n"
+
+ # Align Centre
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.ALIGN_C) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\n>> Scene text. <<\n\n"
+
+ # Indent Left
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.INDENT_L) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\n> Scene text.\n\n"
+
+ # Indent Right
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.INDENT_R) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\n> Scene text. <\n\n"
+
+ # Text (Reset)
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "#### Scene Title\n\nScene text.\n\n"
+
+ # Invalid Actions
+ # ===============
+
+ # No Document Handle
+ nwGUI.docEditor._docHandle = None
+ assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_TXT) is False
+ nwGUI.docEditor._docHandle = sHandle
+
+ # Wrong Action Type
+ assert nwGUI.docEditor.docAction(None) is False
+
+ # Unknown Action
+ assert nwGUI.docEditor.docAction(nwDocAction.NO_ACTION) is False
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_Actions
+
+
+@pytest.mark.gui
+def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
+ """Test the document insert functions.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Insert Text
+ # ===========
+
+ theText = "### A Scene\n\n%s" % ipsumText[0]
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # No Document Handle
+ nwGUI.docEditor._docHandle = None
+ assert nwGUI.docEditor.setCursorPosition(24) is True
+ assert nwGUI.docEditor.insertText("Stuff") is False
+ nwGUI.docEditor._docHandle = sHandle
+
+ # Insert String
+ assert nwGUI.docEditor.setCursorPosition(24) is True
+ assert nwGUI.docEditor.insertText(", ipsumer,") is True
+ assert nwGUI.docEditor.getText() == theText[:24] + ", ipsumer," + theText[24:]
+
+ # Single Quotes
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(41) is True
+ assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LS) is True
+ assert nwGUI.docEditor.setCursorPosition(53) is True
+ assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RS) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019")
+
+ # Double Quotes
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(41) is True
+ assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LD) is True
+ assert nwGUI.docEditor.setCursorPosition(53) is True
+ assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RD) is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d")
+
+ # Invalid Inserts
+ assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
+ assert nwGUI.docEditor.insertText(123) is False
+
+ # Insert KeyWords
+ # ===============
+
+ theText = "### A Scene\n\n\n%s" % ipsumText[0]
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorLine(2)
+
+ # Invalid Keyword
+ assert nwGUI.docEditor.insertKeyWord("stuff") is False
+ assert nwGUI.docEditor.getText() == theText
+
+ # Valid Keyword
+ assert nwGUI.docEditor.insertKeyWord(nwKeyWords.POV_KEY) is True
+ assert nwGUI.docEditor.insertText("Jane\n")
+ assert nwGUI.docEditor.getText() == theText.replace(
+ "\n\n\n", "\n\n@pov: Jane\n\n", 1
+ )
+
+ # Invalid Block
+ with monkeypatch.context() as mp:
+ mp.setattr(QTextBlock, "isValid", lambda *a, **k: False)
+ assert nwGUI.docEditor.insertKeyWord(nwKeyWords.POV_KEY) is False
+
+ # Insert In-Block
+ assert nwGUI.docEditor.setCursorPosition(20) is True
+ assert nwGUI.docEditor.insertKeyWord(nwKeyWords.CHAR_KEY) is True
+ assert nwGUI.docEditor.insertText("John")
+ assert nwGUI.docEditor.getText() == theText.replace(
+ "\n\n\n", "\n\n@pov: Jane\n@char: John\n\n", 1
+ )
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_Insert
+
+
+@pytest.mark.gui
+def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
+ """Test the text manipulation functions.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Clear Surrounding
+ # =================
+
+ # No Selection
+ theText = "### A Scene\n\n%s" % ipsumText[0]
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+
+ theCursor = nwGUI.docEditor.textCursor()
+ assert nwGUI.docEditor._clearSurrounding(theCursor, 1) is False
+
+ # Clear Characters, 1 Layer
+ repText = theText.replace("consectetur", "=consectetur=")
+ assert nwGUI.docEditor.replaceText(repText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.select(QTextCursor.WordUnderCursor)
+ assert nwGUI.docEditor._clearSurrounding(theCursor, 1) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Clear Characters, 2 Layers
+ repText = theText.replace("consectetur", "==consectetur==")
+ assert nwGUI.docEditor.replaceText(repText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.select(QTextCursor.WordUnderCursor)
+ assert nwGUI.docEditor._clearSurrounding(theCursor, 2) is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Wrap Selection
+ # ==============
+
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+
+ # No Selection
+ with monkeypatch.context() as mp:
+ mp.setattr(nwGUI.docEditor, "_autoSelect", lambda: QTextCursor())
+ assert nwGUI.docEditor._wrapSelection("=", "=") is False
+
+ # Wrap Equal
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._wrapSelection("=") is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=")
+
+ # Wrap Unequal
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._wrapSelection("=", "*") is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur*")
+
+ # Past Paragraph
+ assert nwGUI.docEditor.replaceText(theText) is True
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.setPosition(13, QTextCursor.MoveAnchor)
+ theCursor.setPosition(1000, QTextCursor.KeepAnchor)
+ nwGUI.docEditor.setTextCursor(theCursor)
+ assert nwGUI.docEditor._wrapSelection("=") is True
+
+ newText = nwGUI.docEditor.getText()
+ newPara = list(filter(str.strip, newText.split("\n")))
+ assert newPara[1] == "="+ipsumText[0]+"="
+ assert newPara[2] == ipsumText[1]
+
+ # Toggle Format
+ # =============
+
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+
+ # No Selection
+ with monkeypatch.context() as mp:
+ mp.setattr(nwGUI.docEditor, "_autoSelect", lambda: QTextCursor())
+ assert nwGUI.docEditor._toggleFormat(2, "=") is False
+
+ # Wrap Single Equal
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._toggleFormat(1, "=") is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=")
+
+ # Past Paragraph
+ assert nwGUI.docEditor.replaceText(theText) is True
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.setPosition(13, QTextCursor.MoveAnchor)
+ theCursor.setPosition(1000, QTextCursor.KeepAnchor)
+ nwGUI.docEditor.setTextCursor(theCursor)
+ assert nwGUI.docEditor._toggleFormat(1, "=") is True
+
+ newText = nwGUI.docEditor.getText()
+ newPara = list(filter(str.strip, newText.split("\n")))
+ assert newPara[1] == "="+ipsumText[0]+"="
+ assert newPara[2] == ipsumText[1]
+
+ # Wrap Double Equal
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._toggleFormat(2, "=") is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "==consectetur==")
+
+ # Toggle Double Equal
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._toggleFormat(2, "=") is True
+ assert nwGUI.docEditor._toggleFormat(2, "=") is True
+ assert nwGUI.docEditor.getText() == theText
+
+ # Toggle Triple+Double Equal
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._toggleFormat(3, "=") is True
+ assert nwGUI.docEditor._toggleFormat(2, "=") is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=")
+
+ # Toggle Unequal
+ repText = theText.replace("consectetur", "=consectetur==")
+ assert nwGUI.docEditor.replaceText(repText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._toggleFormat(1, "=") is True
+ assert nwGUI.docEditor.getText() == theText.replace("consectetur", "consectetur=")
+ assert nwGUI.docEditor._toggleFormat(1, "=") is True
+ assert nwGUI.docEditor.getText() == repText
+
+ # Replace Quotes
+ # ==============
+
+ # No Selection
+ theText = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=")
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is False
+
+ # First Paragraph Selected
+ # This should not replace anything in second paragraph
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=")
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA)
+ assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True
+
+ newText = nwGUI.docEditor.getText()
+ newPara = list(filter(str.strip, newText.split("\n")))
+ assert newPara[1] == ipsumText[0].replace("ipsum", "")
+ assert newPara[2] == ipsumText[1].replace("ipsum", "=ipsum=")
+
+ # Edge of Document
+ theText = ipsumText[0].replace("Lorem", "=Lorem=")
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL)
+ assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True
+ assert nwGUI.docEditor.getText() == theText.replace("=Lorem=", "")
+
+ # Remove Line Breaks
+ # ==================
+
+ parOne = ipsumText[0].replace(" ", "\n", 5)
+ parTwo = ipsumText[1].replace(" ", "\n", 5)
+
+ # Remove All
+ theText = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.docEditor.setCursorPosition(45) is True
+ assert nwGUI.docEditor._removeInParLineBreaks() is True
+ assert nwGUI.docEditor.getText() == "### A Scene\n\n%s\n" % "\n\n".join(ipsumText[0:2])
+
+ # Remove First Paragraph
+ # Second paragraphs should remain unchanged
+ theText = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
+ assert nwGUI.docEditor.replaceText(theText) is True
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.setPosition(16, QTextCursor.MoveAnchor)
+ theCursor.setPosition(680, QTextCursor.KeepAnchor)
+ nwGUI.docEditor.setTextCursor(theCursor)
+ assert nwGUI.docEditor._removeInParLineBreaks() is True
+
+ newText = nwGUI.docEditor.getText()
+ newPara = list(filter(str.strip, newText.split("\n")))
+ twoBits = parTwo.split()
+ assert newPara[1] == ipsumText[0]
+ assert newPara[2] == twoBits[0]
+ assert newPara[3] == twoBits[1]
+ assert newPara[4] == twoBits[2]
+ assert newPara[5] == twoBits[3]
+ assert newPara[6] == twoBits[4]
+ assert newPara[7] == " ".join(twoBits[5:])
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_TextManipulation
+
+
+@pytest.mark.gui
+def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
+ """Test the block formatting function.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Invalid and Generic
+ # ===================
+
+ theText = "### A Scene\n\n%s" % ipsumText[0]
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Invalid Block
+ assert nwGUI.docEditor.setCursorPosition(0) is True
+ with monkeypatch.context() as mp:
+ mp.setattr(QTextBlock, "isValid", lambda *a, **k: False)
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False
+
+ # Empty Block
+ assert nwGUI.docEditor.setCursorLine(1) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False
+
+ # Keyword
+ assert nwGUI.docEditor.replaceText("@pov: Jane\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False
+ assert nwGUI.docEditor.getText() == "@pov: Jane\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Unsupported Format
+ assert nwGUI.docEditor.replaceText("% Comment\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.NO_ACTION) is False
+
+ # Block Stripping : Left Side
+ # ===========================
+
+ # Strip Comment w/Space
+ assert nwGUI.docEditor.replaceText("% Comment\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Comment\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 3
+
+ # Strip Comment wo/Space
+ assert nwGUI.docEditor.replaceText("%Comment\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Comment\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 4
+
+ # Strip Header 1
+ assert nwGUI.docEditor.replaceText("# Title\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Title\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 3
+
+ # Strip Header 2
+ assert nwGUI.docEditor.replaceText("## Title\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Title\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 2
+
+ # Strip Header 3
+ assert nwGUI.docEditor.replaceText("### Title\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Title\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 1
+
+ # Strip Header 4
+ assert nwGUI.docEditor.replaceText("#### Title\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Title\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 0
+
+ # Strip Text
+ assert nwGUI.docEditor.replaceText("Generic text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Generic text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Strip Left Angle Brackets : Double w/Space
+ assert nwGUI.docEditor.replaceText(">> Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 2
+
+ # Strip Left Angle Brackets : Single w/Space
+ assert nwGUI.docEditor.replaceText("> Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 3
+
+ # Strip Left Angle Brackets : Double wo/Space
+ assert nwGUI.docEditor.replaceText(">>Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 3
+
+ # Strip Left Angle Brackets : Single wo/Space
+ assert nwGUI.docEditor.replaceText(">Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 4
+
+ # Block Stripping : Right Side
+ # ============================
+
+ # Strip Right Angle Brackets : Double w/Space
+ assert nwGUI.docEditor.replaceText("Some text <<\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Strip Right Angle Brackets : Single w/Space
+ assert nwGUI.docEditor.replaceText("Some text <\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Strip Right Angle Brackets : Double wo/Space
+ assert nwGUI.docEditor.replaceText("Some text<<\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Strip Right Angle Brackets : Single wo/Space
+ assert nwGUI.docEditor.replaceText("Some text<\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Block Stripping : Both Sides
+ # ============================
+
+ assert nwGUI.docEditor.replaceText(">> Some text <<\n\n") is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+
+ assert nwGUI.docEditor.replaceText(">Some text <<\n\n") is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+
+ assert nwGUI.docEditor.replaceText(">Some text<\n\n") is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+
+ # New Formats
+ # ===========
+
+ # Comment
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
+ assert nwGUI.docEditor.getText() == "% Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 7
+
+ # Toggle Comment w/Space
+ assert nwGUI.docEditor.replaceText("% Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 3
+
+ # Toggle Comment wo/Space
+ assert nwGUI.docEditor.replaceText("%Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
+ assert nwGUI.docEditor.getText() == "Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 4
+
+ # Header 1
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_H1) is True
+ assert nwGUI.docEditor.getText() == "# Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 7
+
+ # Header 2
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_H2) is True
+ assert nwGUI.docEditor.getText() == "## Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 8
+
+ # Header 3
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_H3) is True
+ assert nwGUI.docEditor.getText() == "### Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 9
+
+ # Header 4
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_H4) is True
+ assert nwGUI.docEditor.getText() == "#### Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 10
+
+ # Left Indent
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.INDENT_L) is True
+ assert nwGUI.docEditor.getText() == "> Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 7
+
+ # Right Indent
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.INDENT_R) is True
+ assert nwGUI.docEditor.getText() == "Some text <\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Right/Left Indent
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.INDENT_L) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.INDENT_R) is True
+ assert nwGUI.docEditor.getText() == "> Some text <\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 7
+
+ # Left Align
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.ALIGN_L) is True
+ assert nwGUI.docEditor.getText() == "Some text <<\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Right Align
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.ALIGN_R) is True
+ assert nwGUI.docEditor.getText() == ">> Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 8
+
+ # Centre Align
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.ALIGN_C) is True
+ assert nwGUI.docEditor.getText() == ">> Some text <<\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 8
+
+ # Left/Right Align (Overrides)
+ assert nwGUI.docEditor.replaceText("Some text\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(5) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.ALIGN_L) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.ALIGN_R) is True
+ assert nwGUI.docEditor.getText() == ">> Some text\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 8
+
+ # Other Checks
+ # ============
+
+ # Final Cursor Position Out of Range
+ assert nwGUI.docEditor.replaceText("#### Title\n\n") is True
+ assert nwGUI.docEditor.setCursorPosition(3) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is True
+ assert nwGUI.docEditor.getText() == "Title\n\n"
+ assert nwGUI.docEditor.getCursorPosition() == 5
+
+ # Second Line
+ # This also needs to add a new block
+ assert nwGUI.docEditor.replaceText("#### Title\n\nThe Text\n\n") is True
+ assert nwGUI.docEditor.setCursorLine(2) is True
+ assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
+ assert nwGUI.docEditor.getText() == "#### Title\n\n% The Text\n\n"
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_BlockFormatting
+
+
+@pytest.mark.gui
+def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
+ """Test the document editor tags functionality.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+
+ # Open project
+ sHandle = "8c659a11cd429"
+ assert nwGUI.openProject(nwMinimal) is True
+ assert nwGUI.openDocument(sHandle) is True
+ qtbot.wait(stepDelay)
+
+ # Create Scene
+ theText = "### A Scene\n\n@char: Jane, John\n\n" + ipsumText[0] + "\n\n"
+ assert nwGUI.docEditor.replaceText(theText) is True
+
+ # Create Character
+ theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n"
+ cHandle = nwGUI.theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
+ assert nwGUI.openDocument(cHandle) is True
+ assert nwGUI.docEditor.replaceText(theText) is True
+ assert nwGUI.saveDocument() is True
+ assert nwGUI.treeView.revealNewTreeItem(cHandle)
+ nwGUI.docEditor.updateTagHighLighting()
+
+ # Follow Tag
+ # ==========
+ assert nwGUI.openDocument(sHandle) is True
+
+ # Empty Block
+ assert nwGUI.docEditor.setCursorLine(1) is True
+ assert nwGUI.docEditor._followTag() is False
+
+ # Not On Tag
+ assert nwGUI.docEditor.setCursorLine(0) is True
+ assert nwGUI.docEditor._followTag() is False
+
+ # On Tag Keyword
+ assert nwGUI.docEditor.setCursorPosition(15) is True
+ assert nwGUI.docEditor._followTag() is False
+
+ # On Unknown Tag
+ assert nwGUI.docEditor.setCursorPosition(28) is True
+ assert nwGUI.docEditor._followTag() is True
+ assert nwGUI.docViewer._docHandle is None
+
+ # On Known Tag, No Follow
+ assert nwGUI.docEditor.setCursorPosition(22) is True
+ assert nwGUI.docEditor._followTag(loadTag=False) is True
+ assert nwGUI.docViewer._docHandle is None
+
+ # On Known Tag, Follow
+ assert nwGUI.docEditor.setCursorPosition(22) is True
+ assert nwGUI.docViewer._docHandle is None
+ assert nwGUI.docEditor._followTag(loadTag=True) is True
+ assert nwGUI.docViewer._docHandle == cHandle
+ assert nwGUI.closeDocViewer() is True
+ assert nwGUI.docViewer._docHandle is None
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_Tags
@pytest.mark.gui
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
new file mode 100644
index 00000000..4e1a09f3
--- /dev/null
+++ b/tests/test_gui/test_gui_guimain.py
@@ -0,0 +1,354 @@
+# -*- coding: utf-8 -*-
+"""
+novelWriter – Main GUI Editor Class Tester
+==========================================
+
+This file is a part of novelWriter
+Copyright 2018–2021, 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 .
+"""
+
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QMessageBox, QDialog
+
+from nw.dialogs.itemeditor import GuiItemEditor
+from nw.gui.doceditor import GuiDocEditor
+from nw.gui.projtree import GuiProjectTree
+from nw.enum import nwItemType, nwWidget
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+
+@pytest.mark.gui
+def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
+ """Test the document editor.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiItemEditor, "result", lambda *args: QDialog.Accepted)
+ monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
+ monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
+
+ # Create new, save, close project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": fncProj})
+ assert nwGUI.saveProject()
+ assert nwGUI.closeProject()
+
+ assert len(nwGUI.theProject.projTree) == 0
+ assert len(nwGUI.theProject.projTree._treeOrder) == 0
+ assert len(nwGUI.theProject.projTree._treeRoots) == 0
+ assert nwGUI.theProject.projTree.trashRoot() is None
+ assert nwGUI.theProject.projPath is None
+ assert nwGUI.theProject.projMeta is None
+ assert nwGUI.theProject.projFile == "nwProject.nwx"
+ assert nwGUI.theProject.projName == ""
+ assert nwGUI.theProject.bookTitle == ""
+ assert len(nwGUI.theProject.bookAuthors) == 0
+ assert not nwGUI.theProject.spellCheck
+
+ # Check the files
+ projFile = os.path.join(fncProj, "nwProject.nwx")
+ testFile = os.path.join(outDir, "guiEditor_Main_Initial_nwProject.nwx")
+ compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+ qtbot.wait(stepDelay)
+
+ # qtbot.stopForInteraction()
+
+ # Re-open project
+ assert nwGUI.openProject(fncProj)
+ qtbot.wait(stepDelay)
+
+ # Check that we loaded the data
+ assert len(nwGUI.theProject.projTree) == 8
+ 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 == fncProj
+ assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
+ assert nwGUI.theProject.projFile == "nwProject.nwx"
+ assert nwGUI.theProject.projName == "New Project"
+ assert nwGUI.theProject.bookTitle == ""
+ assert len(nwGUI.theProject.bookAuthors) == 0
+ assert not nwGUI.theProject.spellCheck
+
+ # Check that tree items have been created
+ assert nwGUI.treeView._getTreeItem("73475cb40a568") is not None
+ assert nwGUI.treeView._getTreeItem("25fc0e7096fc6") is not None
+ assert nwGUI.treeView._getTreeItem("31489056e0916") is not None
+ assert nwGUI.treeView._getTreeItem("98010bd9270f9") is not None
+ assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
+ assert nwGUI.treeView._getTreeItem("44cb730c42048") is not None
+ assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
+ assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
+
+ nwGUI.mainMenu.aSpellCheck.setChecked(True)
+ assert nwGUI.mainMenu._toggleSpellCheck()
+
+ # Change some settings
+ nwGUI.mainConf.hideHScroll = True
+ nwGUI.mainConf.hideVScroll = True
+ nwGUI.mainConf.scrollPastEnd = True
+ nwGUI.mainConf.autoScrollPos = 80
+ nwGUI.mainConf.autoScroll = True
+
+ # Add a Character File
+ nwGUI.switchFocus(nwWidget.TREE)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+
+ # Type something into the document
+ nwGUI.switchFocus(nwWidget.EDITOR)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Jane Doe":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@tag: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "This is a file about Jane.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ # Add a Plot File
+ nwGUI.switchFocus(nwWidget.TREE)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+
+ # Type something into the document
+ nwGUI.switchFocus(nwWidget.EDITOR)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Main Plot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@tag: MainPlot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "This is a file detailing the main plot.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ # Add a World File
+ nwGUI.switchFocus(nwWidget.TREE)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+
+ # Add Some Text
+ nwGUI.docEditor.replaceText("Hello World!")
+ assert nwGUI.docEditor.getText() == "Hello World!"
+ nwGUI.docEditor.replaceText("")
+
+ # Type something into the document
+ nwGUI.switchFocus(nwWidget.EDITOR)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Main Location":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@tag: Home":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "This is a file describing Jane's home.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ # Trigger autosaves before making more changes
+ nwGUI._autoSaveDocument()
+ nwGUI._autoSaveProject()
+
+ # Select the 'New Scene' file
+ nwGUI.switchFocus(nwWidget.TREE)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
+ nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True)
+ nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
+ assert nwGUI.openSelectedItem()
+
+ # Type something into the document
+ nwGUI.switchFocus(nwWidget.EDITOR)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Novel":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "## Chapter":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "@pov: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@plot: MainPlot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "### Scene":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "% How about a comment?":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@pov: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@plot: MainPlot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@location: Home":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "#### Some Section":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "@char: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "This is a paragraph of nonsense text.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in (
+ "This is another paragraph of much longer nonsense text. "
+ "It is in fact 1 very very NONSENSICAL nonsense text! "
+ ):
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "Isn't that nice? ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "Ellipsis? Not a problem either ... ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "How about three hyphens - -":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay)
+ for c in "- for long dash? It works too.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "\"Full line double quoted text.\"":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "'Full line single quoted text.'":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ qtbot.wait(stepDelay)
+ nwGUI.docEditor.wCounter.run()
+ qtbot.wait(stepDelay)
+
+ # Save the document
+ assert nwGUI.docEditor.docChanged()
+ assert nwGUI.saveDocument()
+ assert not nwGUI.docEditor.docChanged()
+ qtbot.wait(stepDelay)
+ nwGUI.rebuildIndex()
+ qtbot.wait(stepDelay)
+
+ # Open and view the edited document
+ nwGUI.switchFocus(nwWidget.VIEWER)
+ assert nwGUI.openDocument("0e17daca5f3e1")
+ assert nwGUI.viewDocument("0e17daca5f3e1")
+ qtbot.wait(stepDelay)
+ assert nwGUI.saveProject()
+ assert nwGUI.closeDocViewer()
+ qtbot.wait(stepDelay)
+
+ # Check a Quick Create and Delete
+ assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ newHandle = nwGUI.treeView.getSelectedHandle()
+ assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
+ assert nwGUI.treeView.deleteItem()
+ assert nwGUI.treeView.setSelectedHandle(newHandle)
+ assert nwGUI.treeView.deleteItem()
+ assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
+ assert nwGUI.saveProject()
+
+ # Check the files
+ projFile = os.path.join(fncProj, "nwProject.nwx")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+
+ projFile = os.path.join(fncProj, "content", "031b4af5197ec.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(fncProj, "content", "1a6562590ef19.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(fncProj, "content", "41cfc0d1f2d12.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiMain_Editing