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
This commit is contained in:
Veronica Berglyd Olsen
2021-07-05 16:10:52 +02:00
committed by GitHub
parent 1d16cc2a03
commit 36be7b0b33
7 changed files with 1892 additions and 657 deletions
+349 -331
View File
@@ -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}")
)
+3 -3
View File
@@ -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()
+6 -7
View File
@@ -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
+9 -10
View File
@@ -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
##
+59
View File
@@ -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
File diff suppressed because it is too large Load Diff
+354
View File
@@ -0,0 +1,354 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Editor Class Tester
==========================================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import 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