"""
novelWriter – Main GUI Editor Class Tester
==========================================
This file is a part of novelWriter
Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors
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 .
""" # noqa
from __future__ import annotations
import shutil
from pathlib import Path
from shutil import copyfile
from unittest.mock import Mock
import pytest
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import QInputDialog
from novelwriter import CONFIG, SHARED, __hexversion__
from novelwriter.common import jsonEncode
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT
from novelwriter.constants import nwFiles
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwTheme, nwView
from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.gui.noveltree import GuiNovelView
from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.shared import _GuiAlert
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.types import QtModCtrl, QtModShift
from tests.mocked import causeOSError
from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles
KEY_DELAY = 1
@pytest.mark.gui
def testGuiMain_ProjectBlocker(nwGUI):
"""Test the blocking of features when there's no project open."""
# Test no-project blocking
assert nwGUI.closeProject() is True
assert nwGUI.saveProject() is False
assert nwGUI.openDocument(None) is False
assert nwGUI.viewDocument(None) is False
assert nwGUI.importDocument() is False
@pytest.mark.gui
def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
"""Test the handling of launch tasks."""
monkeypatch.setattr(GuiWelcome, "exec", lambda *a: None)
CONFIG.lastNotes = "0x0"
buildTestProject(nwGUI, projPath)
# Open Lipsum project
nwGUI.postLaunchTasks(projPath)
assert SHARED.hasProject is True
nwGUI.closeProject()
assert SHARED.hasProject is False
# Open as if called from Welcome
nwGUI._openProjectFromWelcome(projPath)
assert SHARED.hasProject is True
nwGUI.closeProject()
assert SHARED.hasProject is False
# Open as if called from Welcome, invalid path
with monkeypatch.context() as mp:
mp.setattr(nwGUI, "showWelcomeDialog", lambda *a: None)
nwGUI._openProjectFromWelcome(None)
assert SHARED.hasProject is False
# Project open fails
with monkeypatch.context() as mp:
mp.setattr(SHARED, "openProject", lambda *a: False)
assert nwGUI.openProject(projPath) is False
# Handle locked project
with monkeypatch.context() as mp:
mp.setattr(SHARED, "openProject", lambda *a, **k: False)
SHARED._lockedBy = ["a", "b", "c", "d"]
assert nwGUI.openProject(projPath) is False
SHARED._lockedBy = None
assert nwGUI.openProject(projPath) is True
nwGUI.closeProject()
# Check that closes can be blocked
with monkeypatch.context() as mp:
mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is True
assert nwGUI.closeMain() is False
nwGUI.closeProject()
# Check that latest release info updated
assert CONFIG.lastNotes != "0x0"
# Set some config error
CONFIG._hasError = True
CONFIG._errData.append("Foo")
# Check that project open dialog launches
nwGUI.postLaunchTasks(None)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWelcome) is not None, timeout=1000)
welcome = SHARED.findTopLevelWidget(GuiWelcome)
assert isinstance(welcome, GuiWelcome)
welcome.show()
welcome.close()
# Config errors should be cleared
assert SHARED.lastAlert == "Foo"
assert CONFIG._hasError is False
# qtbot.stop()
@pytest.mark.gui
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test handling of project tree items based on GUI focus states."""
buildTestProject(nwGUI, projPath)
sHandle = "000000000000f"
nwGUI.openSelectedItem()
assert nwGUI.docEditor.docHandle is None
# Project Tree has focus
nwGUI._changeView(nwView.PROJECT)
nwGUI._switchFocus(nwFocus.TREE)
nwGUI.projStack.setCurrentIndex(0)
with monkeypatch.context() as mp:
mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle is None
nwGUI.projView.projTree.setSelectedHandle(sHandle)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle == sHandle
nwGUI.closeDocument()
# Novel Tree has focus
nwGUI._changeView(nwView.NOVEL)
with monkeypatch.context() as mp:
mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle is None
model = nwGUI.novelView.novelTree._getModel()
assert model is not None
nwGUI.novelView.novelTree.setCurrentIndex(model.createIndex(2, 0))
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle == sHandle
nwGUI.closeDocument()
# Project Outline has focus
nwGUI._changeView(nwView.OUTLINE)
nwGUI._switchFocus(nwFocus.OUTLINE)
with monkeypatch.context() as mp:
mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle is None
selItem = nwGUI.outlineView.outlineTree.topLevelItem(2)
nwGUI.outlineView.outlineTree.setCurrentItem(selItem)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle == sHandle
nwGUI.closeDocument()
# qtbot.stop()
@pytest.mark.gui
def testGuiMain_UpdateTheme(qtbot, nwGUI):
"""Test updating the theme in the GUI."""
theme = SHARED.theme
CONFIG.themeMode = nwTheme.DARK
CONFIG.darkTheme = DEF_GUI_DARK
CONFIG.lightTheme = DEF_GUI_LIGHT
theme.loadTheme()
assert theme.isDarkTheme is True
nwGUI._processConfigChanges(False, True, False, False)
nwGUI._processConfigChanges(True, True, True, True)
# Check editor syntax
syntax = SHARED.theme.syntaxTheme
assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntax.back
assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back
assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntax.back
assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back
# Update by check
CONFIG.themeMode = nwTheme.LIGHT
nwGUI.checkThemeUpdate()
assert theme.isDarkTheme is False
# Through change event
event = Mock()
event.type.return_value = 210
CONFIG.themeMode = nwTheme.DARK
nwGUI.changeEvent(event)
assert theme.isDarkTheme is True
# qtbot.stop()
@pytest.mark.gui
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
"""Test the document editor."""
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Create new, save, close project
buildTestProject(nwGUI, projPath)
assert nwGUI.saveProject()
assert nwGUI.closeProject()
assert len(SHARED.project.tree) == 0
assert len(SHARED.project.tree._items) == 0
assert len(SHARED.project.tree._nodes) == 0
assert SHARED.project.data.name == ""
assert SHARED.project.data.author == ""
assert SHARED.project.data.spellCheck is False
# Check the files
projFile = projPath / "nwProject.nwx"
testFile = tstPaths.outDir / "guiEditor_Main_Initial_nwProject.nwx"
compFile = tstPaths.refDir / "guiEditor_Main_Initial_nwProject.nwx"
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Re-open project
assert nwGUI.openProject(projPath)
# Check that we loaded the data
assert len(SHARED.project.tree) == 9
assert SHARED.project.tree.model.root.childCount() == 5
assert SHARED.project.tree.trash is not None # Created automatically
assert SHARED.project.data.name == "New Project"
assert SHARED.project.data.author == "Jane Doe"
assert SHARED.project.data.spellCheck is False
# Check that tree items have been created
assert SHARED.project.tree[C.hNovelRoot] is not None
assert SHARED.project.tree[C.hPlotRoot] is not None
assert SHARED.project.tree[C.hCharRoot] is not None
assert SHARED.project.tree[C.hWorldRoot] is not None
assert SHARED.project.tree[C.hTitlePage] is not None
assert SHARED.project.tree[C.hChapterDir] is not None
assert SHARED.project.tree[C.hChapterDoc] is not None
assert SHARED.project.tree[C.hSceneDoc] is not None
nwGUI.mainMenu.aSpellCheck.setChecked(True)
nwGUI.mainMenu._toggleSpellCheck()
# Change some settings
CONFIG.hideHScroll = True
CONFIG.hideVScroll = True
CONFIG.autoScroll = False
# Add a Character File
nwGUI._changeView(nwView.PROJECT)
nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree.setSelectedHandle(C.hCharRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
nwGUI.projView.projTree.expandAll()
nwGUI.openSelectedItem()
# Text Editor
# ===========
# Syntax Highlighting
CONFIG.dialogStyle = 3
CONFIG.dialogLine = "–"
CONFIG.altDialogOpen = "<|"
CONFIG.altDialogClose = "|>"
docEditor = nwGUI.docEditor
docEditor._qDocument.syntaxHighlighter.initHighlighter()
# Type something into the document
nwGUI.docEditor.setFocus()
qtbot.keyClick(docEditor, "a", modifier=QtModCtrl, delay=KEY_DELAY)
for c in "# Jane Doe":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@tag: Jane":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "This is a file about Jane.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Add a Plot File
nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
nwGUI.projView.projTree.expandAll()
nwGUI.openSelectedItem()
# Type something into the document
nwGUI.docEditor.setFocus()
qtbot.keyClick(docEditor, "a", modifier=QtModCtrl, delay=KEY_DELAY)
for c in "# Main Plot":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@tag: MainPlot":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "This is a file [i]detailing[/i] the main plot.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Add a World File
nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree.setSelectedHandle(C.hWorldRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
nwGUI.projView.projTree.expandAll()
nwGUI.openSelectedItem()
# Add Some Text
docEditor.replaceText("Hello World!")
assert docEditor.getText() == "Hello World!"
docEditor.replaceText("")
# Type something into the document
nwGUI.docEditor.setFocus()
qtbot.keyClick(docEditor, "a", modifier=QtModCtrl, delay=KEY_DELAY)
for c in "# Main Location":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@tag: Home":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "This is a file describing Jane's home.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Trigger autosaves before making more changes
nwGUI._autoSaveDocument()
nwGUI._autoSaveProject()
# Select the 'New Scene' file
nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree.setSelectedHandle(C.hSceneDoc)
nwGUI.openSelectedItem()
# Type something into the document
nwGUI.docEditor.setFocus()
qtbot.keyClick(docEditor, "a", modifier=QtModCtrl, delay=KEY_DELAY)
for c in "# Novel":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "## Chapter":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@pov: Jane":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@plot: MainPlot":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "### Scene":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "% How about a comment?":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@pov: Jane":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@plot: MainPlot":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@location: Home":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "#### Some Section":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "@char: Jane":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "This is a paragraph of nonsense text.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Don't allow Shift+Enter to insert a line separator (issue #1150)
for c in "This is another paragraph":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Enter, modifier=QtModShift, delay=KEY_DELAY)
for c in "with a line separator in it.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Auto-Replace
# ============
for c in (
"This is another paragraph of much longer nonsense text. "
"It is in fact 1 very very NONSENSICAL nonsense text! "
):
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "Isn't that nice? ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "We can even go on to a ---- hotizontal bar. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "Ellipsis? Not a problem either ... ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "How about three hyphens - -":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Left, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Right, delay=KEY_DELAY)
for c in "- for long dash? It works too. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "Even four hyphens - - -":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Left, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Left, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Right, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Right, delay=KEY_DELAY)
for c in "- for a horizontal works!":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Dialogue
# ========
for c in '"Full line double quoted text."':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "'Full line single quoted text.'":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Insert spaces before and after quotes
CONFIG.fmtPadBefore = "\u201d"
CONFIG.fmtPadAfter = "\u201c"
docEditor.initEditor()
for c in 'Some "double quoted text with spaces padded".':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
CONFIG.fmtPadBefore = ""
CONFIG.fmtPadAfter = ""
docEditor.initEditor()
# Dialogue Line
for c in "-- Hi, I am a character speaking.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Narrator Break
CONFIG.narratorBreak = "–"
docEditor = nwGUI.docEditor
docEditor._qDocument.syntaxHighlighter.initHighlighter()
for c in "-- Hi, I am also a character speaking, -- said another character. -- How are you?":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Special Formatting
# ==================
# Insert spaces before colon, but ignore tags
CONFIG.fmtPadBefore = ":"
docEditor.initEditor()
for c in "@object: NoSpaceAdded":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "% synopsis: Space before this is OK.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "%Footnote.abc: A simple footnote.":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "Add space before this colon: See?":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "But don't add a double space : See?":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
CONFIG.fmtPadBefore = ""
docEditor.initEditor()
# Indent and Align
# ================
nwGUI._switchFocus(nwView.EDITOR)
for c in '\t"Tab-indented text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in '>"Paragraph-indented text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in '>>"Right-aligned text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in "\t'Tab-indented text'":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in ">'Paragraph-indented text'":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in ">>'Right-aligned text'":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
docEditor._wCounterDoc.run()
# Check Files
# ===========
# Save the document
assert docEditor.docChanged
nwGUI.saveDocument()
assert docEditor.docChanged is False
nwGUI.forceSaveDocument()
assert docEditor.docChanged is False
nwGUI.rebuildIndex()
# Open and view the edited document
nwGUI.docViewer.setFocus()
assert nwGUI.openDocument(C.hSceneDoc)
assert nwGUI.viewDocument(C.hSceneDoc)
assert nwGUI.saveProject()
assert nwGUI.closeViewerPanel()
# Check the files
projFile = projPath / "nwProject.nwx"
testFile = tstPaths.outDir / "guiEditor_Main_Final_nwProject.nwx"
compFile = tstPaths.refDir / "guiEditor_Main_Final_nwProject.nwx"
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=(*XML_IGNORE, " 0
@pytest.mark.gui
def testGuiMain_FocusView(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test switching focus and view of the main window."""
buildTestProject(nwGUI, projPath)
nwGUI.openDocument(C.hSceneDoc)
nwGUI.viewDocument(C.hSceneDoc)
# Toggle Focus
# ============
nwGUI.docEditor.setFocus()
assert nwGUI.docEditor.anyFocus()
# Simulate focus change to viewer
nwGUI._appFocusChanged(None, nwGUI.docViewer)
assert nwGUI.docEditor.docHeader.itemTitle._state is False
assert nwGUI.docViewer.docHeader.itemTitle._state is True
# Simulate focus change to editor
nwGUI._appFocusChanged(None, nwGUI.docEditor)
assert nwGUI.docEditor.docHeader.itemTitle._state is True
assert nwGUI.docViewer.docHeader.itemTitle._state is False
# Focus Tree
# ==========
assert nwGUI.projStack.currentWidget() == nwGUI.projView
# Switch from editor to project tree
nwGUI.docEditor.setFocus()
nwGUI._switchFocus(nwFocus.TREE)
assert nwGUI.projStack.currentWidget() == nwGUI.projView
# Triggering again should switch to novel view
nwGUI._switchFocus(nwFocus.TREE)
assert nwGUI.projStack.currentWidget() == nwGUI.novelView
# Switch from editor to novel view
nwGUI.docEditor.setFocus()
nwGUI._switchFocus(nwFocus.TREE)
assert nwGUI.projStack.currentWidget() == nwGUI.novelView
# Triggering again should switch back to project tree
nwGUI._switchFocus(nwFocus.TREE)
assert nwGUI.projStack.currentWidget() == nwGUI.projView
# If in search mode, should default to project tree
nwGUI._changeView(nwView.SEARCH)
nwGUI._switchFocus(nwFocus.TREE)
assert nwGUI.projStack.currentWidget() == nwGUI.projView
# Focus Document
# ==============
nwGUI._switchFocus(nwFocus.TREE)
def mockEmitEditorFocus(*a):
nwGUI._appFocusChanged(None, nwGUI.docEditor)
def mockEmitViewerFocus(*a):
nwGUI._appFocusChanged(None, nwGUI.docViewer)
# Switch to viewer
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: True)
mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: False)
mp.setattr(nwGUI.docEditor, "setFocus", mockEmitEditorFocus)
mp.setattr(nwGUI.docViewer, "setFocus", mockEmitViewerFocus)
nwGUI._switchFocus(nwFocus.DOCUMENT)
assert nwGUI.docEditor.docHeader.itemTitle._state is False
assert nwGUI.docViewer.docHeader.itemTitle._state is True
# Call again to switch to editor
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: False)
mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: True)
mp.setattr(nwGUI.docEditor, "setFocus", mockEmitEditorFocus)
mp.setattr(nwGUI.docViewer, "setFocus", mockEmitViewerFocus)
nwGUI._switchFocus(nwFocus.DOCUMENT)
assert nwGUI.docEditor.docHeader.itemTitle._state is True
assert nwGUI.docViewer.docHeader.itemTitle._state is False
# Default to editor
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: False)
mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: False)
mp.setattr(nwGUI.docEditor, "setFocus", mockEmitEditorFocus)
mp.setattr(nwGUI.docViewer, "setFocus", mockEmitViewerFocus)
nwGUI._switchFocus(nwFocus.DOCUMENT)
assert nwGUI.docEditor.docHeader.itemTitle._state is True
assert nwGUI.docViewer.docHeader.itemTitle._state is False
# Focus Outline
# =============
nwGUI._switchFocus(nwFocus.OUTLINE)
assert nwGUI.mainStack.currentWidget() == nwGUI.outlineView
# Pass Actions
# ============
# Pass to editor
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: True)
mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: False)
nwGUI._passDocumentAction(nwDocAction.SEL_ALL)
assert nwGUI.docEditor.textCursor().hasSelection() is True
# Pass to viewer
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: False)
mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: True)
nwGUI._passDocumentAction(nwDocAction.SEL_ALL)
assert nwGUI.docViewer.textCursor().hasSelection() is True
# qtbot.stop()