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