Merge branch 'main' into merge_patches

This commit is contained in:
Veronica Berglyd Olsen
2022-08-17 23:21:10 +02:00
178 changed files with 17390 additions and 9704 deletions
+13 -13
View File
@@ -29,7 +29,7 @@ from PyQt5.QtWidgets import QAction, QMessageBox, qApp
from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.core import countWords
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout
from novelwriter.constants import nwKeyWords, nwUnicode
keyDelay = 2
@@ -43,6 +43,7 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Open project
assert nwGUI.openProject(nwMinimal)
@@ -184,10 +185,10 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
assert "Could not save document." in caplog.text
# Change header level
assert nwGUI.theProject.projTree[sHandle].itemLayout == nwItemLayout.DOCUMENT
assert nwGUI.theProject.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT
nwGUI.docEditor.replaceText(longText[1:])
assert nwGUI.docEditor.saveText() is True
assert nwGUI.theProject.projTree[sHandle].itemLayout == nwItemLayout.DOCUMENT
assert nwGUI.theProject.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT
# Regular save
assert nwGUI.docEditor.saveText() is True
@@ -235,9 +236,9 @@ def testGuiEditor_MetaData(qtbot, monkeypatch, nwGUI, nwMinimal):
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
assert nwGUI.theProject.tree[sHandle].cursorPos != 10
nwGUI.docEditor.saveCursorPosition()
assert nwGUI.theProject.projTree[sHandle].cursorPos == 10
assert nwGUI.theProject.tree[sHandle].cursorPos == 10
assert nwGUI.docEditor.setCursorLine(None) is False
assert nwGUI.docEditor.setCursorLine(2) is True
@@ -1142,11 +1143,11 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
# Create Character
theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n"
cHandle = nwGUI.theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
cHandle = nwGUI.theProject.newFile("Jane Doe", "afb3043c7b2b3")
assert nwGUI.openDocument(cHandle) is True
assert nwGUI.docEditor.replaceText(theText) is True
assert nwGUI.saveDocument() is True
assert nwGUI.treeView.revealNewTreeItem(cHandle)
assert nwGUI.projView.revealNewTreeItem(cHandle)
nwGUI.docEditor.updateTagHighLighting()
# Follow Tag
@@ -1225,8 +1226,8 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
# Open a document and populate it
sHandle = "8c659a11cd429"
nwGUI.theProject.projTree[sHandle]._initCount = 0 # Clear item's count
nwGUI.theProject.projTree[sHandle]._wordCount = 0 # Clear item's count
nwGUI.theProject.tree[sHandle]._initCount = 0 # Clear item's count
nwGUI.theProject.tree[sHandle]._wordCount = 0 # Clear item's count
assert nwGUI.openDocument(sHandle) is True
qtbot.wait(stepDelay)
@@ -1252,9 +1253,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.docEditor.wCounterDoc.run()
# nwGUI.docEditor._updateDocCounts(cC, wC, pC)
qtbot.wait(stepDelay)
assert nwGUI.theProject.projTree[sHandle]._charCount == cC
assert nwGUI.theProject.projTree[sHandle]._wordCount == wC
assert nwGUI.theProject.projTree[sHandle]._paraCount == pC
assert nwGUI.theProject.tree[sHandle]._charCount == cC
assert nwGUI.theProject.tree[sHandle]._wordCount == wC
assert nwGUI.theProject.tree[sHandle]._paraCount == pC
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})"
# Select all text
@@ -1285,7 +1286,6 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum) is True
assert nwGUI.openDocument("4c4f28287af27") is True
origText = nwGUI.docEditor.getText()
+8 -9
View File
@@ -43,21 +43,20 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Open project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum)
# Rebuild the index
nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
assert nwGUI.theIndex._tagIndex != {}
assert nwGUI.theIndex._refIndex != {}
assert nwGUI.theProject.index._tagsIndex._tags != {}
assert nwGUI.theProject.index._itemIndex._items != {}
# Select a document in the project tree
nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
nwGUI.projView.setSelectedHandle("88243afbe5ed8")
# Middle-click the selected item
theItem = nwGUI.treeView._getTreeItem("88243afbe5ed8")
theRect = nwGUI.treeView.visualItemRect(theItem)
qtbot.mouseClick(nwGUI.treeView.viewport(), Qt.MidButton, pos=theRect.center())
theItem = nwGUI.projView.projTree._getTreeItem("88243afbe5ed8")
theRect = nwGUI.projView.projTree.visualItemRect(theItem)
qtbot.mouseClick(nwGUI.projView.projTree.viewport(), Qt.MidButton, pos=theRect.center())
assert nwGUI.docViewer.docHandle() == "88243afbe5ed8"
# Reload the text
@@ -118,7 +117,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu
assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
assert nwGUI.projView.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.Trigger)
# Select "Bod" link
@@ -141,7 +140,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
nwGUI.docViewer.reloadText()
# Change document title
nwItem = nwGUI.theProject.projTree["4c4f28287af27"]
nwItem = nwGUI.theProject.tree["4c4f28287af27"]
nwItem.setName("Test Title")
assert nwItem.itemName == "Test Title"
nwGUI.docViewer.updateDocInfo("4c4f28287af27")
+172 -90
View File
@@ -19,20 +19,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
import pytest
from shutil import copyfile
from tools import cmpFiles
from tools import cmpFiles, buildTestProject, XML_IGNORE, writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox, QDialog
from PyQt5.QtWidgets import QMessageBox, QInputDialog
from novelwriter.gui import (
GuiDocEditor, GuiProjectTree, GuiNovelTree, GuiOutline
)
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.dialogs.itemeditor import GuiItemEditor
from novelwriter.gui import GuiDocEditor, GuiNovelView, GuiOutlineView
from novelwriter.enum import nwItemType, nwView, nwWidget
from novelwriter.tools import GuiProjectWizard
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.dialogs import GuiEditLabel
keyDelay = 2
typeDelay = 1
@@ -57,10 +57,9 @@ def testGuiMain_ProjectBlocker(monkeypatch, nwGUI):
assert nwGUI.mergeDocuments() is False
assert nwGUI.splitDocument() is False
assert nwGUI.openSelectedItem() is False
assert nwGUI.editItem() is False
assert nwGUI.editItemLabel() is False
assert nwGUI.requestNovelTreeRefresh() is False
assert nwGUI.rebuildIndex() is False
assert nwGUI.rebuildOutline() is False
assert nwGUI.showProjectSettingsDialog() is False
assert nwGUI.showProjectDetailsDialog() is False
assert nwGUI.showBuildProjectDialog() is False
@@ -71,83 +70,117 @@ def testGuiMain_ProjectBlocker(monkeypatch, nwGUI):
@pytest.mark.gui
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj):
def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
"""Test creating a new project.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
# No data
with monkeypatch.context() as mp:
mp.setattr(GuiProjectWizard, "exec_", lambda *a: None)
assert nwGUI.newProject(projData=None) is False
# Close project
with monkeypatch.context() as mp:
nwGUI.hasProject = True
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
assert nwGUI.newProject(projData={"projPath": fncProj}) is False
# No project path
assert nwGUI.newProject(projData={}) is False
# Project file already exists
projFile = os.path.join(fncProj, nwGUI.theProject.projFile)
writeFile(projFile, "Stuff")
assert nwGUI.newProject(projData={"projPath": fncProj}) is False
os.unlink(projFile)
# An unreachable path should also fail
projPath = os.path.join(fncProj, "stuff", "stuff", "stuff")
assert nwGUI.newProject(projData={"projPath": projPath}) is False
# This one should work just fine
assert nwGUI.newProject(projData={"projPath": fncProj}) is True
assert os.path.isfile(os.path.join(fncProj, nwGUI.theProject.projFile))
assert os.path.isdir(os.path.join(fncProj, "content"))
# END Test testGuiMain_NewProject
@pytest.mark.gui
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test handling of project tree items based on GUI focus states.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) is True
assert nwGUI.saveProject() is True
buildTestProject(nwGUI, fncProj)
sHandle = "0e17daca5f3e1"
sHandle = "000000000000f"
assert nwGUI.openSelectedItem() is False
# Project Tree has focus
nwGUI._changeView(nwView.PROJECT)
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.projTabs.setCurrentIndex(0)
nwGUI.projStack.setCurrentIndex(0)
with monkeypatch.context() as mp:
mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
nwGUI.treeView._getTreeItem(sHandle).setSelected(True)
nwGUI.projView.projTree._getTreeItem(sHandle).setSelected(True)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
# Novel Tree has focus
nwGUI.projTabs.setCurrentIndex(1)
nwGUI.novelView.refreshTree(True)
nwGUI._changeView(nwView.NOVEL)
nwGUI.novelView.novelTree.refreshTree(rootHandle=None, overRide=True)
with monkeypatch.context() as mp:
mp.setattr(GuiNovelTree, "hasFocus", lambda *a: True)
mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
actItem = nwGUI.novelView.topLevelItem(0)
chpItem = actItem.child(0)
selItem = chpItem.child(0)
nwGUI.novelView.setCurrentItem(selItem)
selItem = nwGUI.novelView.novelTree.topLevelItem(2)
nwGUI.novelView.novelTree.setCurrentItem(selItem)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
# Project Outline has focus
nwGUI._changeView(nwView.OUTLINE)
nwGUI.switchFocus(nwWidget.OUTLINE)
with monkeypatch.context() as mp:
mp.setattr(GuiOutline, "hasFocus", lambda *a: True)
mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
actItem = nwGUI.projView.topLevelItem(0)
chpItem = actItem.child(0)
selItem = chpItem.child(0)
nwGUI.projView.setCurrentItem(selItem)
selItem = nwGUI.outlineView.outlineTree.topLevelItem(2)
nwGUI.outlineView.outlineTree.setCurrentItem(selItem)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
# qtbot.stopForInteraction()
# qtbot.stop()
# END Test testGuiMain_ProjectTreeItems
@pytest.mark.gui
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mockRnd):
"""Test the document 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)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Create new, save, close project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
buildTestProject(nwGUI, 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 len(nwGUI.theProject.tree) == 0
assert len(nwGUI.theProject.tree._treeOrder) == 0
assert len(nwGUI.theProject.tree._treeRoots) == 0
assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.projFile == "nwProject.nwx"
@@ -161,7 +194,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
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])
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
qtbot.wait(stepDelay)
# qtbot.stopForInteraction()
@@ -171,27 +204,27 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
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 len(nwGUI.theProject.tree) == 8
assert len(nwGUI.theProject.tree._treeOrder) == 8
assert len(nwGUI.theProject.tree._treeRoots) == 4
assert nwGUI.theProject.tree.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
assert nwGUI.theProject.bookTitle == "New Novel"
assert len(nwGUI.theProject.bookAuthors) == 1
assert nwGUI.theProject.spellCheck is False
# 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
assert nwGUI.projView.projTree._getTreeItem("0000000000008") is not None
assert nwGUI.projView.projTree._getTreeItem("0000000000009") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000a") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000b") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000c") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000d") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000e") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000f") is not None
nwGUI.mainMenu.aSpellCheck.setChecked(True)
assert nwGUI.mainMenu._toggleSpellCheck()
@@ -204,9 +237,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
# Add a Character File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -226,9 +259,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
# Add a Plot File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("0000000000009").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -248,9 +281,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
# Add a World File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("000000000000b").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Add Some Text
@@ -279,10 +312,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
# 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)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("0000000000008").setExpanded(True)
nwGUI.projView.projTree._getTreeItem("000000000000d").setExpanded(True)
nwGUI.projView.projTree._getTreeItem("000000000000f").setSelected(True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -417,21 +450,21 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
# Open and view the edited document
nwGUI.switchFocus(nwWidget.VIEWER)
assert nwGUI.openDocument("0e17daca5f3e1")
assert nwGUI.viewDocument("0e17daca5f3e1")
assert nwGUI.openDocument("000000000000f")
assert nwGUI.viewDocument("000000000000f")
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.projView.projTree.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.projView.getSelectedHandle()
assert nwGUI.theProject.tree["0000000000020"] is not None
assert nwGUI.projView.deleteItem()
assert nwGUI.projView.setSelectedHandle(newHandle)
assert nwGUI.projView.deleteItem()
assert nwGUI.theProject.tree["0000000000024"] is not None # Trash
assert nwGUI.saveProject()
# Check the files
@@ -439,32 +472,81 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
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, 13])
assert cmpFiles(testFile, compFile, ignoreStart=(*XML_IGNORE, "<spellCheck"))
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")
projFile = os.path.join(fncProj, "content", "000000000000f.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_000000000000f.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_000000000000f.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")
projFile = os.path.join(fncProj, "content", "0000000000020.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000020.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000020.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")
projFile = os.path.join(fncProj, "content", "0000000000021.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000021.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000021.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")
projFile = os.path.join(fncProj, "content", "0000000000022.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000022.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000022.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile)
# qtbot.stopForInteraction()
# END Test testGuiMain_Editing
@pytest.mark.gui
def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test toggling focus mode in main window.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
buildTestProject(nwGUI, fncProj)
assert nwGUI.isFocusMode is False
# Focus Mode
# ==========
# No document open, so not allowing focus mode
assert nwGUI.toggleFocusMode() is False
# Open a file in editor and viewer
assert nwGUI.openDocument("000000000000f")
assert nwGUI.viewDocument("000000000000f")
# Enable focus mode
assert nwGUI.toggleFocusMode() is True
assert nwGUI.treePane.isVisible() is False
assert nwGUI.statusBar.isVisible() is False
assert nwGUI.mainMenu.isVisible() is False
assert nwGUI.viewsBar.isVisible() is False
assert nwGUI.splitView.isVisible() is False
# Disable focus mode
assert nwGUI.toggleFocusMode() is True
assert nwGUI.treePane.isVisible() is True
assert nwGUI.statusBar.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True
assert nwGUI.viewsBar.isVisible() is True
assert nwGUI.splitView.isVisible() is True
# Full Screen Mode
# ================
assert nwGUI.mainConf.isFullScreen is False
nwGUI.toggleFullScreenMode()
assert nwGUI.mainConf.isFullScreen is True
nwGUI.toggleFullScreenMode()
assert nwGUI.mainConf.isFullScreen is False
# qtbot.stopForInteraction()
# END Test testGuiMain_FocusFullMode
+9 -12
View File
@@ -26,7 +26,7 @@ from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor, QTextBlock
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
from tools import writeFile
from tools import writeFile, buildTestProject
from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.enum import nwDocAction, nwDocInsert
@@ -48,7 +48,6 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
# Test Document Action with No Project
assert nwGUI.docEditor.docAction(nwDocAction.COPY) is False
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum) is True
qtbot.wait(stepDelay)
@@ -381,7 +380,6 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum)
assert nwGUI.openDocument("4c4f28287af27")
qtbot.wait(stepDelay)
@@ -460,18 +458,17 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
@pytest.mark.gui
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
"""Test the Insert menu.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
buildTestProject(nwGUI, fncProj)
assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
assert nwGUI.openDocument("0e17daca5f3e1") is True
assert nwGUI.projView.projTree._getTreeItem("000000000000f") is not None
assert nwGUI.openDocument("000000000000f") is True
nwGUI.docEditor.clear()
# Test Faulty Inserts
@@ -479,10 +476,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert nwGUI.docEditor.getText() == "hello world"
nwGUI.docEditor.clear()
assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT)
assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
assert nwGUI.docEditor.isEmpty()
assert not nwGUI.docEditor.insertText(None)
assert nwGUI.docEditor.insertText(None) is False
assert nwGUI.docEditor.isEmpty()
# qtbot.stopForInteraction()
@@ -680,7 +677,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert not nwGUI.importDocument()
# Open the document from before, and add some text to it
nwGUI.openDocument("0e17daca5f3e1")
nwGUI.openDocument("000000000000f")
nwGUI.docEditor.setText("Bar")
assert nwGUI.docEditor.getText() == "Bar"
@@ -712,7 +709,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
theBits = theMessage.split("<br>")
assert len(theBits) == 2
assert theBits[0] == "The currently open file is saved in:"
assert theBits[1] == os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
assert theBits[1] == os.path.join(fncProj, "content", "000000000000f.nwd")
# qtbot.stopForInteraction()
+126 -81
View File
@@ -19,74 +19,98 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
import pytest
from tools import writeFile
from tools import buildTestProject, writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtGui import QFocusEvent
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtWidgets import QMessageBox, QToolTip
from novelwriter.enum import nwWidget, nwItemType
from novelwriter.dialogs import GuiEditLabel
from novelwriter.gui.noveltree import NovelTreeColumn
@pytest.mark.gui
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test navigating the novel tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwGUI.openProject(nwMinimal)
nwGUI.theProject.projTree.setSeed(42)
nwTree = nwGUI.novelView
buildTestProject(nwGUI, fncProj)
##
# Show/Hide Scrollbars
##
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
nwTree.mainConf.hideVScroll = True
nwTree.mainConf.hideHScroll = True
nwTree.initTree()
assert not nwTree.verticalScrollBar().isVisible()
assert not nwTree.horizontalScrollBar().isVisible()
writeFile(
os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"),
"# Jane Doe\n\n@tag: Jane\n\n"
)
writeFile(
os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"), (
"### Scene One\n\n"
"@pov: Jane\n"
"@focus: Jane\n\n"
"% Synopsis: This is a scene."
)
)
nwTree.mainConf.hideVScroll = False
nwTree.mainConf.hideHScroll = False
nwTree.initTree()
assert nwTree.verticalScrollBar().isEnabled()
assert nwTree.horizontalScrollBar().isEnabled()
novelView = nwGUI.novelView
novelTree = novelView.novelTree
novelBar = novelView.novelBar
##
# Populate Tree
##
# Show/Hide Scrollbars
# ====================
nwGUI.projTabs.setCurrentIndex(nwGUI.idxNovelView)
nwGUI.mainConf.hideVScroll = True
nwGUI.mainConf.hideHScroll = True
novelView.initSettings()
assert not novelTree.verticalScrollBar().isVisible()
assert not novelTree.horizontalScrollBar().isVisible()
nwGUI.mainConf.hideVScroll = False
nwGUI.mainConf.hideHScroll = False
novelView.initSettings()
assert novelTree.verticalScrollBar().isEnabled()
assert novelTree.horizontalScrollBar().isEnabled()
# Populate Tree
# =============
nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView)
nwGUI.rebuildIndex()
nwTree._populateTree()
assert nwTree.topLevelItemCount() == 1
novelTree._populateTree(rootHandle=None)
assert novelTree.topLevelItemCount() == 3
# Rebuild should preserve selection
topItem = nwTree.topLevelItem(0)
topItem = novelTree.topLevelItem(0)
assert not topItem.isSelected()
topItem.setSelected(True)
assert nwTree.selectedItems()[0] == topItem
assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0)
assert novelTree.selectedItems()[0] == topItem
assert novelView.getSelectedHandle() == ("000000000000c", 0)
nwTree.refreshTree()
assert nwTree.topLevelItem(0).isSelected()
# Refresh using the slot for the butoom
novelBar._refreshNovelTree()
assert novelTree.topLevelItem(0).isSelected()
##
# Open Items
##
# Open Items
# ==========
# Clear selection
nwTree.clearSelection()
scItem = nwTree.topLevelItem(0).child(0).child(0)
novelTree.clearSelection()
scItem = novelTree.topLevelItem(2)
scItem.setSelected(True)
assert scItem.isSelected()
# Clear selection with mouse
vPort = nwTree.viewport()
vPort = novelTree.viewport()
qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
assert not scItem.isSelected()
@@ -94,8 +118,8 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
scItem.setSelected(True)
assert scItem.isSelected()
assert nwGUI.docEditor.docHandle() is None
nwTree._treeDoubleClick(scItem, 0)
assert nwGUI.docEditor.docHandle() == "8c659a11cd429"
novelTree._treeDoubleClick(scItem, 0)
assert nwGUI.docEditor.docHandle() == "000000000000f"
# Open item with middle mouse button
scItem.setSelected(True)
@@ -104,58 +128,79 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10)
assert nwGUI.docViewer.docHandle() is None
scRect = nwTree.visualItemRect(scItem)
oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole)
scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", ""))
scRect = novelTree.visualItemRect(scItem)
oldData = scItem.data(novelTree.C_TITLE, novelTree.D_HANDLE)
scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, None)
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
assert nwGUI.docViewer.docHandle() is None
scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData)
scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, oldData)
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
assert nwGUI.docViewer.docHandle() == "8c659a11cd429"
assert nwGUI.docViewer.docHandle() == "000000000000f"
##
# Populate Tree
##
# Last Column
# ===========
# Add weird titles to first file to check hnadling of non-standard
# order of title levels.
writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), (
"#### Section wo/Scene\n\n"
"### Scene wo/Chapter\n\n"
"## Chapter wo/Title\n\n"
"# Title\n\n"
"#### Section w/Title, wo/Scene\n\n"
"### Scene w/Title, wo/Chapter\n\n"
"## Chapter\n\n"
"#### Section w/Chapter, wo/Scene\n\n"
"### Scene\n\n"
"#### Section\n\n"
))
nwGUI.rebuildIndex()
nwTree._populateTree()
assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene"
assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter"
assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title"
assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title"
novelBar.setLastColType(NovelTreeColumn.HIDDEN)
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True
assert novelTree.lastColType == NovelTreeColumn.HIDDEN
assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "")
tTitle = nwTree.topLevelItem(3)
assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene"
assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter"
assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter"
novelBar.setLastColType(NovelTreeColumn.POV)
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
assert novelTree.lastColType == NovelTreeColumn.POV
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Point of View: Jane"
)
tChap = tTitle.child(2)
assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene"
assert tChap.child(1).text(nwTree.C_TITLE) == "Scene"
novelBar.setLastColType(NovelTreeColumn.FOCUS)
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
assert novelTree.lastColType == NovelTreeColumn.FOCUS
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Focus: Jane"
)
tScene = tChap.child(1)
assert tScene.child(0).text(nwTree.C_TITLE) == "Section"
novelBar.setLastColType(NovelTreeColumn.PLOT)
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
assert novelTree.lastColType == NovelTreeColumn.PLOT
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"", "Plot: "
)
##
# Close
##
novelTree._lastCol = None
assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "")
# qtbot.stopForInteraction()
# Item Meta
# =========
ttText = ""
def showText(pos, text):
nonlocal ttText
ttText = text
mIndex = novelTree.model().index(2, novelTree.C_MORE)
with monkeypatch.context() as mp:
mp.setattr(QToolTip, "showText", showText)
novelTree._treeItemClicked(mIndex)
assert ttText == (
"<p><b>Point of View</b>: Jane<br><b>Focus</b>: Jane</p>"
"<p><b>Synopsis</b>: This is a scene.</p>"
)
# Other Checks
# ============
scItem = novelTree.topLevelItem(2)
scItem.setSelected(True)
assert scItem.isSelected()
novelTree.focusOutEvent(QFocusEvent(QEvent.None_, Qt.MouseFocusReason))
assert not scItem.isSelected()
# Close
# =====
# qtbot.stop()
nwGUI.closeProject()
# END Test testGuiNovelTree_TreeItems
+206 -51
View File
@@ -19,20 +19,144 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import time
import pytest
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QAction, QTreeWidgetItem, QMessageBox
from tools import buildTestProject, writeFile
from novelwriter.enum import nwOutline
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QMessageBox, QAction
keyDelay = 2
typeDelay = 1
stepDelay = 20
from novelwriter.enum import nwItemClass, nwOutline, nwView
@pytest.mark.gui
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
"""Test the outline view.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
nwGUI.rebuildIndex()
nwGUI._changeView(nwView.OUTLINE)
outlineView = nwGUI.outlineView
outlineTree = outlineView.outlineTree
outlineData = outlineView.outlineData
outlineMenu = outlineView.outlineBar.mColumns
# Toggle scrollbars
nwGUI.mainConf.hideVScroll = True
nwGUI.mainConf.hideHScroll = True
outlineView.initSettings()
assert outlineTree.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineTree.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineData.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineData.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
nwGUI.mainConf.hideVScroll = False
nwGUI.mainConf.hideHScroll = False
outlineView.initSettings()
assert outlineTree.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineTree.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineData.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineData.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
# Check focus
with monkeypatch.context() as mp:
mp.setattr(QWidget, "hasFocus", lambda *a: True)
assert outlineView.treeHasFocus() is True
outlineView.setTreeFocus() # Can't check. just ensures that it doesn't error
# Option State
# ============
pOptions = nwGUI.theProject.options
colNames = [h.name for h in nwOutline]
colItems = [h for h in nwOutline]
colWidth = {h: outlineTree.DEF_WIDTH[h] for h in nwOutline}
colHidden = {h: outlineTree.DEF_HIDDEN[h] for h in nwOutline}
assert outlineTree.topLevelItemCount() > 0
# Save header state not allowed
outlineTree._lastBuild = 0
outlineTree._saveHeaderState()
assert pOptions.getValue("GuiOutline", "headerOrder", []) == []
# Allow saving header state
outlineTree._lastBuild = time.time()
outlineTree._saveHeaderState()
assert pOptions.getValue("GuiOutline", "headerOrder", []) == colNames
assert outlineTree._treeOrder == colItems
assert outlineTree._colWidth == colWidth
assert outlineTree._colHidden == colHidden
# Get default values
optItems = pOptions.getValue("GuiOutline", "headerOrder", [])
optWidth = pOptions.getValue("GuiOutline", "columnWidth", {})
optHidden = pOptions.getValue("GuiOutline", "columnHidden", {})
# Add invalid column name
pOptions.setValue("GuiOutline", "headerOrder", optItems + ["blabla"])
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Add duplicate column name
pOptions.setValue("GuiOutline", "headerOrder", optItems + [optItems[-1]])
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Invalid column width data
pOptions.setValue("GuiOutline", "headerOrder", optItems)
pOptions.setValue("GuiOutline", "columnWidth", {"blabla": None})
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Invalid column width data
pOptions.setValue("GuiOutline", "headerOrder", optItems)
pOptions.setValue("GuiOutline", "columnWidth", optWidth)
pOptions.setValue("GuiOutline", "columnHidden", {"bloabla": None})
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Valid settings
pOptions.setValue("GuiOutline", "headerOrder", optItems)
pOptions.setValue("GuiOutline", "columnWidth", optWidth)
pOptions.setValue("GuiOutline", "columnHidden", optHidden)
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Header Menu
# ===========
# Trigger the menu entry for all hidden columns
for hItem in nwOutline:
if outlineTree.DEF_HIDDEN[hItem]:
outlineMenu.actionMap[hItem].activate(QAction.Trigger)
# Now no columns should be hidden
outlineTree._saveHeaderState()
assert not any(pOptions.getValue("GuiOutline", "columnHidden", None).values())
# qtbot.stop()
# END Test testGuiOutline_Main
@pytest.mark.gui
def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the outline view.
"""
# Block message box
@@ -43,73 +167,104 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
nwGUI.mainConf.lastPath = nwLipsum
nwGUI.rebuildIndex()
nwGUI.mainTabs.setCurrentIndex(nwGUI.idxTabProj)
nwGUI._changeView(nwView.OUTLINE)
assert nwGUI.projView.topLevelItemCount() > 0
outlineView = nwGUI.outlineView
outlineBar = outlineView.outlineBar
outlineTree = outlineView.outlineTree
outlineData = outlineView.outlineData
# Context Menu
nwGUI.projView._headerRightClick(QPoint(1, 1))
nwGUI.projView.headerMenu.actionMap[nwOutline.CCOUNT].activate(QAction.Trigger)
nwGUI.projView.headerMenu.close()
qtbot.mouseClick(nwGUI.projView, Qt.LeftButton)
lipHandle = "b3643d0f92e32"
nwGUI.projView._loadHeaderState()
assert not nwGUI.projView._colHidden[nwOutline.CCOUNT]
# Check defaults in dropdown list
assert outlineBar.novelValue.itemData(0) == lipHandle
assert outlineBar.novelValue.itemData(1) is None # Separator
assert outlineBar.novelValue.itemData(2) == "" # All novels
# Add a second novel folder
newHandle = nwGUI.theProject.newRoot(nwItemClass.NOVEL)
nwGUI.projView.revealNewTreeItem(newHandle)
# Check new values in dropdown list
assert outlineBar.novelValue.itemData(0) == lipHandle
assert outlineBar.novelValue.itemData(1) == newHandle
assert outlineBar.novelValue.itemData(2) is None # Separator
assert outlineBar.novelValue.itemData(3) == "" # All novels
# Add a bunch of files in a header order that hits all tree combos
docList = [
("Section 1", 4), ("Scene 1", 3), ("Chapter 1", 2), ("Part 1", 1),
("Section 2", 4), ("Scene 2", 3), ("Chapter 2", 2),
("Section 3", 4), ("Scene 3", 3),
("Section 4", 4),
]
for dTitle, hLevel in docList:
aHandle = nwGUI.theProject.newFile(dTitle, newHandle)
hHash = "#"*hLevel
writeFile(os.path.join(nwLipsum, "content", f"{aHandle}.nwd"), f"{hHash} {dTitle}\n\n")
nwGUI.projView.revealNewTreeItem(aHandle)
nwGUI.rebuildIndex()
# Build the second novel
outlineBar.novelValue.setCurrentIndex(1)
outlineBar._refreshRequested()
# Go back to Lipsum
outlineBar.novelValue.setCurrentIndex(0)
outlineBar._refreshRequested()
# Check Details
# =============
# First Item
nwGUI.rebuildOutline()
selItem = nwGUI.projView.topLevelItem(0)
assert isinstance(selItem, QTreeWidgetItem)
outlineTree.refreshTree()
selItem = outlineTree.topLevelItem(0)
nwGUI.projView.setCurrentItem(selItem)
assert nwGUI.projMeta.titleLabel.text() == "<b>Title</b>"
assert nwGUI.projMeta.titleValue.text() == "Lorem Ipsum"
assert nwGUI.projMeta.fileValue.text() == "Lorem Ipsum"
assert nwGUI.projMeta.itemValue.text() == "Finished"
outlineTree.setCurrentItem(selItem)
assert outlineData.titleLabel.text() == "<b>Title</b>"
assert outlineData.titleValue.text() == "Lorem Ipsum"
assert outlineData.fileValue.text() == "Lorem Ipsum"
assert outlineData.itemValue.text() == "Finished"
assert nwGUI.projMeta.cCValue.text() == "230"
assert nwGUI.projMeta.wCValue.text() == "40"
assert nwGUI.projMeta.pCValue.text() == "3"
assert outlineData.cCValue.text() == "230"
assert outlineData.wCValue.text() == "40"
assert outlineData.pCValue.text() == "3"
# Scene One
actItem = nwGUI.projView.topLevelItem(1)
chpItem = actItem.child(0)
selItem = chpItem.child(0)
selItem = outlineTree.topLevelItem(4)
nwGUI.projView.setCurrentItem(selItem)
tHandle, tLine = nwGUI.projView.getSelectedHandle()
outlineTree.setCurrentItem(selItem)
tHandle, tLine = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8"
assert tLine == 0
assert nwGUI.projMeta.titleLabel.text() == "<b>Scene</b>"
assert nwGUI.projMeta.titleValue.text() == "Scene One"
assert nwGUI.projMeta.fileValue.text() == "Scene One"
assert nwGUI.projMeta.itemValue.text() == "Finished"
assert outlineData.titleLabel.text() == "<b>Scene</b>"
assert outlineData.titleValue.text() == "Scene One"
assert outlineData.fileValue.text() == "Scene One"
assert outlineData.itemValue.text() == "Finished"
# Click POV Link
assert nwGUI.projMeta.povKeyValue.text() == "<a href='#pov=Bod'>Bod</a>"
nwGUI.projMeta._tagClicked("#pov=Bod")
assert outlineData.povKeyValue.text() == "<a href='Bod'>Bod</a>"
outlineView._tagClicked("Bod")
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
# Scene One, Section Two
actItem = nwGUI.projView.topLevelItem(1)
chpItem = actItem.child(0)
scnItem = chpItem.child(0)
selItem = scnItem.child(0)
selItem = outlineTree.topLevelItem(5)
nwGUI.projView.setCurrentItem(selItem)
tHandle, tLine = nwGUI.projView.getSelectedHandle()
outlineTree.setCurrentItem(selItem)
tHandle, tLine = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8"
assert tLine == 12
assert nwGUI.projMeta.titleLabel.text() == "<b>Section</b>"
assert nwGUI.projMeta.titleValue.text() == "Scene One, Section Two"
assert nwGUI.projMeta.fileValue.text() == "Scene One"
assert nwGUI.projMeta.itemValue.text() == "Finished"
assert outlineData.titleLabel.text() == "<b>Section</b>"
assert outlineData.titleValue.text() == "Scene One, Section Two"
assert outlineData.fileValue.text() == "Scene One"
assert outlineData.itemValue.text() == "Finished"
nwGUI.projView._treeDoubleClick(selItem, 0)
outlineTree._treeDoubleClick(selItem, 0)
assert nwGUI.docEditor.docHandle() == "88243afbe5ed8"
# qtbot.stopForInteraction()
# qtbot.stop()
# END Test testGuiOutline_Main
# END Test testGuiOutline_Content
+493 -183
View File
@@ -22,218 +22,528 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest
import os
from tools import writeFile
from tools import buildTestProject
from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QAction, QMessageBox
from PyQt5.QtWidgets import QMessageBox, QMenu
from novelwriter.guimain import GuiMain
from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
from novelwriter.dialogs import GuiEditLabel
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.enum import nwItemType, nwItemClass
@pytest.mark.gui
def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
"""Test adding and removing items from the project tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwGUI.theProject.projTree.setSeed(42)
nwTree = nwGUI.treeView
nwTree = nwGUI.projView
##
# Add New Items
##
# Try to add item with no project
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
# Try to add and move item with no project
assert not nwTree.newTreeItem(nwItemType.FILE, None)
assert not nwTree.moveTreeItem(1)
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# Open a project
assert nwGUI.openProject(nwMinimal)
# No itemType set
nwTree.projTree.clearSelection()
assert nwTree.projTree.newTreeItem(None) is False
# Root Items
# ==========
# No class set
assert nwTree.projTree.newTreeItem(nwItemType.ROOT) is False
# Create root item
assert nwTree.projTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
assert "0000000000010" in nwGUI.theProject.tree
# File/Folder Items
# =================
# No location selected for new item
nwTree.clearSelection()
assert not nwTree.newTreeItem(nwItemType.FILE, None)
assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is False
assert "Did not find anywhere" in caplog.text
# No itemType set or ROOT, but no class
assert not nwTree.newTreeItem(None, None)
assert not nwTree.newTreeItem(nwItemType.ROOT, None)
# Create new folder as child of Novel folder
nwTree.setSelectedHandle("0000000000008")
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
assert nwGUI.theProject.tree["0000000000011"].itemParent == "0000000000008"
assert nwGUI.theProject.tree["0000000000011"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000011"].itemClass == nwItemClass.NOVEL
# Select a location
chItem = nwTree._getTreeItem("a6d311a93600a")
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
chItem.setExpanded(True)
# Add a new file in the new folder
nwTree.setSelectedHandle("0000000000011")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwGUI.theProject.tree["0000000000012"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000012"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000012"].itemClass == nwItemClass.NOVEL
# Create new item with no class set (defaults to NOVEL)
assert nwTree.newTreeItem(nwItemType.FILE, None)
assert nwTree.newTreeItem(nwItemType.FOLDER, None)
# Add a new chapter next to the other new file
nwTree.setSelectedHandle("0000000000012")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=2) is True
assert nwGUI.theProject.tree["0000000000013"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000013"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000013"].itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument("0000000000013")
assert nwGUI.docEditor.getText() == "## New Chapter\n\n"
# Check that we have the correct tree order
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
]
# Add a new scene next to the other new file
nwTree.setSelectedHandle("0000000000012")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=3) is True
assert nwGUI.theProject.tree["0000000000014"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000014"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000014"].itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument("0000000000014")
assert nwGUI.docEditor.getText() == "### New Scene\n\n"
# Add roots
assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid
# Add a new file to the characters folder
nwTree.setSelectedHandle("000000000000a")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) is True
assert nwGUI.theProject.tree["0000000000015"].itemParent == "000000000000a"
assert nwGUI.theProject.tree["0000000000015"].itemRoot == "000000000000a"
assert nwGUI.theProject.tree["0000000000015"].itemClass == nwItemClass.CHARACTER
assert nwGUI.openDocument("0000000000015")
assert nwGUI.docEditor.getText() == "# New Note\n\n"
# Change max depth and try to add a subfolder that is too deep
monkeypatch.setattr("novelwriter.constants.nwConst.MAX_DEPTH", 2)
chItem = nwTree._getTreeItem("71ee45a3c0db9")
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
# Make sure the sibling folder bug trap works
nwTree.setSelectedHandle("0000000000013")
nwGUI.theProject.tree["0000000000013"].setParent(None) # This should not happen
caplog.clear()
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert "Internal error" in caplog.text
nwGUI.theProject.tree["0000000000013"].setParent("0000000000011")
##
# Move Items
##
# Cancel during creation
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("", False))
nwTree.setSelectedHandle("0000000000013")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
nwTree.setSelectedHandle("8c659a11cd429")
# Get the trash folder
nwTree.projTree._addTrashRoot()
trashHandle = nwGUI.theProject.trashFolder()
nwTree.setSelectedHandle(trashHandle)
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert "Cannot add new files or folders to the Trash folder" in caplog.text
# Shift focus and try to move item
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
assert not nwTree.moveTreeItem(1)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
]
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Other Checks
# ============
# Move second item up twice (should give same result)
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9"
]
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9"
]
# Also check error handling in reveal function
assert nwTree.revealNewTreeItem("abc") is False
# Move it back down four times (last two should be the same)
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
]
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "8c659a11cd429", "71ee45a3c0db9"
]
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
]
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
]
# Move up twice, and undo
nwTree._lastMove = {}
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
]
# Move a root item (top level items are different) twice
nwTree.flushTreeOrder()
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
nwTree.setSelectedHandle("9d5247ab588e0")
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
nwTree.flushTreeOrder()
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
nwTree.flushTreeOrder()
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11
##
# Delete and Trash
##
# Add some content to the new file
nwGUI.openDocument("73475cb40a568")
nwGUI.docEditor.setText("# Hello World\n")
nwGUI.saveDocument()
nwGUI.saveProject()
assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
# Delete the items we added earlier
nwTree.clearSelection()
assert not nwTree.emptyTrash() # No folder yet
assert not nwTree.deleteItem(None)
assert not nwTree.deleteItem("1111111111111")
assert nwTree.deleteItem("73475cb40a568") # New File
assert nwTree.deleteItem("71ee45a3c0db9") # New Folder
assert nwTree.deleteItem("811786ad1ae74") # Custom Root
assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
assert "811786ad1ae74" not in nwGUI.theProject.projTree._treeOrder
# The file is in trash, empty it
assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
assert nwTree.emptyTrash()
assert not nwTree.emptyTrash() # Already empty
assert not os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder
# Should not be allowed to add files and folders to Trash
trashHandle = nwGUI.theProject.projTree.trashRoot()
chItem = nwTree._getTreeItem(trashHandle)
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
assert not nwTree.newTreeItem(nwItemType.FILE, None)
assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
# Close the project
nwGUI.closeProject()
##
# Orphaned Files
##
# Add an orphaned file
orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd")
writeFile(orphFile, "# Hello World\n")
# Open the project again
nwGUI.openProject(nwMinimal)
# Check that the orphaned file was found and added to the tree
nwTree.flushTreeOrder()
assert "1234567890abc" in nwGUI.theProject.projTree._treeOrder
orItem = nwTree._getTreeItem("1234567890abc")
assert orItem.text(nwTree.C_NAME) == "Recovered File 1"
##
# Unexpected Error Handling
##
# Add an item with an invalid type
assert not nwTree.newTreeItem(nwItemType.NO_TYPE, nwItemClass.NOVEL)
assert "Failed to add new item" in caplog.messages[-1]
# Add new file after one that has no parent handle
chItem = nwTree._getTreeItem("44cb730c42048")
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
nwTree.theProject.projTree["44cb730c42048"]._parent = None
assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
nwTree.clearSelection()
# Add a file with no parent, and fail to find a suitable parent item
monkeypatch.setattr("novelwriter.core.tree.NWTree.findRoot", lambda *a: None)
assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
assert not nwTree.newTreeItem(nwItemType.FOLDER, nwItemClass.NOVEL)
# Add an item that cannot be displayed in the tree
nHandle = nwGUI.theProject.newFile("Test", None)
assert nwTree.revealNewTreeItem(nHandle) is False
# Clean up
# qtbot.stopForInteraction()
nwGUI.closeProject()
# END Test testGuiProjTree_TreeItems
# END Test testGuiProjTree_NewItems
@pytest.mark.gui
def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
"""Test adding and removing items from the project tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwTree = nwGUI.projView
# Try to move item with no project
assert nwTree.projTree.moveTreeItem(1) is False
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# Move Documents
# ==============
# Add some files
nwTree.setSelectedHandle("000000000000d")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Move with no selections
nwTree.projTree.clearSelection()
assert nwTree.projTree.moveTreeItem(1) is False
# Move second item up twice (should give same result)
nwTree.setSelectedHandle("000000000000f")
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000f", "000000000000e",
"0000000000010", "0000000000011", "0000000000012",
]
assert nwTree.projTree.moveTreeItem(-1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000f", "000000000000e",
"0000000000010", "0000000000011", "0000000000012",
]
# Restore
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Move fifth item down twice (should give same result)
nwTree.setSelectedHandle("0000000000011")
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
assert nwTree.projTree.moveTreeItem(1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
# Restore
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Move down again, and restore via undo
nwTree.setSelectedHandle("0000000000011")
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
assert nwTree.projTree.undoLastMove() is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Root Folder
# ===========
nwTree.setSelectedHandle("0000000000008")
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder up
assert nwTree.projTree.moveTreeItem(-1) is False
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder down
assert nwTree.projTree.moveTreeItem(1) is True
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 1
# Move novel folder up again
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Clean up
# qtbot.stopForInteraction()
nwGUI.closeProject()
# END Test testGuiProjTree_MoveItems
@pytest.mark.gui
def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
"""Test adding and removing items from the project tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwTree = nwGUI.projView
# Try to run with no project
assert nwTree.emptyTrash() is False
assert nwTree.deleteItem() is False
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# Try emptying the trash already now, when there is no trash folder
assert nwTree.emptyTrash() is False
# Add some files
nwTree.setSelectedHandle("000000000000d")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Delete item without focus -> blocked
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
nwTree.setSelectedHandle("0000000000012")
assert nwTree.deleteItem() is False
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# No selection made
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.deleteItem() is False
assert "no item to delete" in caplog.text
# Not a valid handle
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.deleteItem("0000000000000") is False
assert "Could not find tree item" in caplog.text
# Delete Folder/Root
# ==================
# Deleting non-empty folders is blocked
assert nwTree.deleteItem("0000000000008") is False # Novel Root
assert nwTree.deleteItem("000000000000a") is True # Character Root
# Delete File
# ===========
# Block adding trash folder
funcPointer = nwTree.projTree._addTrashRoot
nwTree.projTree._addTrashRoot = lambda *a: None
assert nwTree.deleteItem("0000000000012") is False
nwTree.projTree._addTrashRoot = funcPointer
# Delete last two documents, which also adds the trash folder
assert nwTree.deleteItem("0000000000012") is True
assert nwTree.deleteItem("0000000000011") is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010"
]
trashHandle = nwGUI.theProject.tree.trashRoot()
assert nwTree.getTreeFromHandle(trashHandle) == [
trashHandle, "0000000000012", "0000000000011"
]
# Delete the first file again (permanent), and ask for permission
# Also open the document in the editor, which should trigger a close
assert os.path.isfile(os.path.join(prjDir, "content", "0000000000012.nwd"))
assert "0000000000012" in nwGUI.theProject.tree
assert nwGUI.docEditor.docHandle() is None
assert nwGUI.openDocument("0000000000012") is True
assert nwGUI.docEditor.docHandle() == "0000000000012"
assert nwTree.deleteItem("0000000000012") is True
assert nwGUI.docEditor.docHandle() is None
assert not os.path.isfile(os.path.join(prjDir, "content", "0000000000012.nwd"))
assert "0000000000012" not in nwGUI.theProject.tree
assert nwTree.getTreeFromHandle(trashHandle) == [
trashHandle, "0000000000011"
]
# Delete the second file, and skip asking for permission
assert os.path.isfile(os.path.join(prjDir, "content", "0000000000011.nwd"))
assert "0000000000011" in nwGUI.theProject.tree
assert nwTree.deleteItem("0000000000011", alreadyAsked=True) is True
assert not os.path.isfile(os.path.join(prjDir, "content", "0000000000011.nwd"))
assert "0000000000011" not in nwGUI.theProject.tree
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
# Delete Folder
# =============
trashHandle = nwGUI.theProject.tree.trashRoot()
# Add a folder with two files
nwTree.setSelectedHandle("0000000000009")
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
nwTree.setSelectedHandle("0000000000014")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000015.nwd"))
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000016.nwd"))
# Delete the folder, which moves everything to Trash
assert nwTree.getTreeFromHandle("0000000000014") == [
"0000000000014", "0000000000015", "0000000000016"
]
assert nwTree.deleteItem("0000000000014") is True
assert nwTree.getTreeFromHandle(trashHandle) == [
trashHandle, "0000000000014", "0000000000015", "0000000000016"
]
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000015.nwd"))
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000016.nwd"))
# Delete again, which should delete folder and all files
assert nwTree.deleteItem("0000000000014") is True
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
assert not os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000015.nwd"))
assert not os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000016.nwd"))
# Add an empty folder, which can be deleted with no further restrictions
nwTree.setSelectedHandle("0000000000009")
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.getTreeFromHandle("0000000000009") == ["0000000000009", "0000000000017"]
nwTree.setSelectedHandle("0000000000017")
assert nwTree.deleteItem("0000000000017") is True
assert nwTree.getTreeFromHandle("0000000000009") == ["0000000000009"]
# Empty Trash
# ===========
# Try to empty trash that is already empty
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
assert nwTree.emptyTrash() is False
# Move the two remaining scene documents to trash
assert nwTree.deleteItem("000000000000f") is True
assert nwTree.deleteItem("0000000000010") is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e"
]
assert nwTree.getTreeFromHandle(trashHandle) == [
trashHandle, "000000000000f", "0000000000010"
]
# Empty trash, but select no on question
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
assert nwTree.emptyTrash() is False
# Empty the trash proper
assert nwTree.emptyTrash() is True
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
# Try to delete a file, but block the underlying deletion of the file on disk
assert os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.document.NWDoc.deleteDocument", lambda *a: False)
assert nwTree.deleteItem("000000000000e") is True
assert nwTree.deleteItem("000000000000e") is True
assert os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
# Delete proper
assert nwTree.projTree._deleteTreeItem("000000000000e") is True
assert not os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
# Clean up
# qtbot.stopForInteraction()
nwGUI.closeProject()
# END Test testGuiProjTree_DeleteItems
@pytest.mark.gui
def testGuiProjTree_ContextMenu(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
"""Test the building of the project tree context menu. All this does
is test that the menu builds. It doesn't open the actual menu,
"""
# Block message box
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# Handles for new objects
hNovelRoot = "0000000000008"
hTitlePage = "000000000000c"
hChapterDir = "000000000000d"
hChapterFile = "000000000000e"
hCharRoot = "000000000000a"
hCharNote = "0000000000011"
hNovelNote = "0000000000012"
projTree = nwGUI.projView.projTree
projTree._getTreeItem(hNovelRoot).setExpanded(True)
projTree._getTreeItem(hChapterDir).setExpanded(True)
projTree._addTrashRoot()
hTrashRoot = projTree.theProject.tree.trashRoot()
projTree.setSelectedHandle(hCharRoot)
projTree.newTreeItem(nwItemType.FILE)
projTree.setSelectedHandle(hNovelRoot)
projTree.newTreeItem(nwItemType.FILE, isNote=True)
def itemPos(tHandle):
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
# No item under menu
assert projTree._openContextMenu(projTree.viewport().rect().bottomRight()) is False
# Generate the possible menu combinarions
assert projTree._openContextMenu(itemPos(hTrashRoot)) is True
assert projTree._openContextMenu(itemPos(hNovelRoot)) is True
assert projTree._openContextMenu(itemPos(hNovelNote)) is True
assert projTree._openContextMenu(itemPos(hTitlePage)) is True
assert projTree._openContextMenu(itemPos(hChapterDir)) is True
assert projTree._openContextMenu(itemPos(hChapterFile)) is True
assert projTree._openContextMenu(itemPos(hCharRoot)) is True
assert projTree._openContextMenu(itemPos(hCharNote)) is True
# Check the keyboard shortcut handler as well
projTree.setSelectedHandle(hNovelRoot)
assert projTree.openContextOnSelected() is True
projTree.clearSelection()
assert projTree.openContextOnSelected() is False
# Direct Edit Functions
# =====================
# Trigger the dedicated functions the menu entries connect to
nwItem = projTree.theProject.tree[hNovelNote]
# Toggle exported flag
assert nwItem.isExported is True
projTree._toggleItemExported(hNovelNote)
assert nwItem.isExported is False
# Change item status
assert nwItem.itemStatus == "s000000"
projTree._changeItemStatus(hNovelNote, "s000001")
assert nwItem.itemStatus == "s000001"
# Change item importance
assert nwItem.itemImport == "i000004"
projTree._changeItemImport(hNovelNote, "i000005")
assert nwItem.itemImport == "i000005"
# Change item layout
assert nwItem.itemLayout == nwItemLayout.NOTE
projTree._changeItemLayout(hNovelNote, nwItemLayout.DOCUMENT)
assert nwItem.itemLayout == nwItemLayout.DOCUMENT
projTree._changeItemLayout(hNovelNote, nwItemLayout.NOTE)
assert nwItem.itemLayout == nwItemLayout.NOTE
# qtbot.stop()
# END Test testGuiProjTree_ContextMenu
+9 -8
View File
@@ -22,24 +22,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import time
import pytest
from tools import buildTestProject
from PyQt5.QtWidgets import QMessageBox
from novelwriter.core import NWDoc
from novelwriter.enum import nwItemClass, nwState
from novelwriter.enum import nwState
@pytest.mark.gui
def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj):
def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test the the various features of the status bar.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) is True
cHandle = nwGUI.theProject.newFile("A Note", nwItemClass.CHARACTER, "71ee45a3c0db9")
buildTestProject(nwGUI, fncProj)
cHandle = nwGUI.theProject.newFile("A Note", "000000000000a")
newDoc = NWDoc(nwGUI.theProject, cHandle)
newDoc.writeDocument("# A Note\n\n")
nwGUI.treeView.revealNewTreeItem(cHandle)
nwGUI.projView.revealNewTreeItem(cHandle)
nwGUI.rebuildIndex(beQuiet=True)
# Reference Time
@@ -90,10 +91,10 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj):
# Project Stats
nwGUI.statusBar.mainConf.incNotesWCount = False
nwGUI._updateStatusWordCount()
assert nwGUI.statusBar.statsText.text() == "Words: 6 (+6)"
assert nwGUI.statusBar.statsText.text() == "Words: 9 (+9)"
nwGUI.statusBar.mainConf.incNotesWCount = True
nwGUI._updateStatusWordCount()
assert nwGUI.statusBar.statsText.text() == "Words: 8 (+8)"
assert nwGUI.statusBar.statsText.text() == "Words: 11 (+11)"
# qtbot.stopForInteraction()
+34 -33
View File
@@ -35,6 +35,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
"""Test the theme and icon classes.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
@@ -80,7 +81,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert thePalette.window().color() == QColor(54, 54, 54)
assert thePalette.windowText().color() == QColor(174, 174, 174)
assert thePalette.base().color() == QColor(62, 62, 62)
assert thePalette.alternateBase().color() == QColor(67, 67, 67)
assert thePalette.alternateBase().color() == QColor(78, 78, 78)
assert thePalette.text().color() == QColor(174, 174, 174)
assert thePalette.toolTipBase().color() == QColor(255, 255, 192)
assert thePalette.toolTipText().color() == QColor(21, 21, 13)
@@ -92,82 +93,82 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert thePalette.link().color() == QColor(44, 152, 247)
assert thePalette.linkVisited().color() == QColor(44, 152, 247)
assert nwGUI.theTheme.statNone == [150, 152, 150]
assert nwGUI.theTheme.statSaved == [39, 135, 78]
assert nwGUI.theTheme.statUnsaved == [138, 32, 32]
assert nwGUI.mainTheme.statNone == [150, 152, 150]
assert nwGUI.mainTheme.statSaved == [39, 135, 78]
assert nwGUI.mainTheme.statUnsaved == [138, 32, 32]
# Check Syntax Colours
assert nwGUI.theTheme.colBack == [45, 45, 45]
assert nwGUI.theTheme.colText == [204, 204, 204]
assert nwGUI.theTheme.colLink == [102, 153, 204]
assert nwGUI.theTheme.colHead == [102, 153, 204]
assert nwGUI.theTheme.colHeadH == [102, 153, 204]
assert nwGUI.theTheme.colEmph == [249, 145, 57]
assert nwGUI.theTheme.colDialN == [242, 119, 122]
assert nwGUI.theTheme.colDialD == [153, 204, 153]
assert nwGUI.theTheme.colDialS == [255, 204, 102]
assert nwGUI.theTheme.colHidden == [153, 153, 153]
assert nwGUI.theTheme.colKey == [242, 119, 122]
assert nwGUI.theTheme.colVal == [204, 153, 204]
assert nwGUI.theTheme.colSpell == [242, 119, 122]
assert nwGUI.theTheme.colError == [153, 204, 153]
assert nwGUI.theTheme.colRepTag == [102, 204, 204]
assert nwGUI.theTheme.colMod == [249, 145, 57]
assert nwGUI.mainTheme.colBack == [45, 45, 45]
assert nwGUI.mainTheme.colText == [204, 204, 204]
assert nwGUI.mainTheme.colLink == [102, 153, 204]
assert nwGUI.mainTheme.colHead == [102, 153, 204]
assert nwGUI.mainTheme.colHeadH == [102, 153, 204]
assert nwGUI.mainTheme.colEmph == [249, 145, 57]
assert nwGUI.mainTheme.colDialN == [242, 119, 122]
assert nwGUI.mainTheme.colDialD == [153, 204, 153]
assert nwGUI.mainTheme.colDialS == [255, 204, 102]
assert nwGUI.mainTheme.colHidden == [153, 153, 153]
assert nwGUI.mainTheme.colKey == [242, 119, 122]
assert nwGUI.mainTheme.colVal == [204, 153, 204]
assert nwGUI.mainTheme.colSpell == [242, 119, 122]
assert nwGUI.mainTheme.colError == [153, 204, 153]
assert nwGUI.mainTheme.colRepTag == [102, 204, 204]
assert nwGUI.mainTheme.colMod == [249, 145, 57]
# Test Icon class
theIcons = nwGUI.theTheme.theIcons
iconCache = nwGUI.mainTheme.iconCache
novelwriter.CONFIG.guiIcons = "invalid"
assert theIcons.updateTheme() is True
assert iconCache.updateTheme() is True
assert novelwriter.CONFIG.guiIcons == "typicons_light"
# Ask for a non-existent key
anImg = theIcons.loadDecoration("nonsense", 20, 20)
anImg = iconCache.loadDecoration("nonsense", 20, 20)
assert isinstance(anImg, QPixmap)
assert anImg.isNull()
# Add a non-existent file and request it
theIcons.DECO_MAP["nonsense"] = "nofile.jpg"
anImg = theIcons.loadDecoration("nonsense", 20, 20)
iconCache.IMAGE_MAP["nonsense"] = "nofile.jpg"
anImg = iconCache.loadDecoration("nonsense", 20, 20)
assert isinstance(anImg, QPixmap)
assert anImg.isNull()
# Get a real image, with different size parameters
anImg = theIcons.loadDecoration("wiz-back", 20, None)
anImg = iconCache.loadDecoration("wiz-back", 20, None)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.width() == 20
assert anImg.height() >= 56
anImg = theIcons.loadDecoration("wiz-back", None, 70)
anImg = iconCache.loadDecoration("wiz-back", None, 70)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.height() == 70
assert anImg.width() >= 24
anImg = theIcons.loadDecoration("wiz-back", 30, 70)
anImg = iconCache.loadDecoration("wiz-back", 30, 70)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.height() == 70
assert anImg.width() == 30
anImg = theIcons.loadDecoration("wiz-back", None, None)
anImg = iconCache.loadDecoration("wiz-back", None, None)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.height() >= 1500
assert anImg.width() >= 500
# Load icons
anIcon = theIcons.getIcon("nonsense")
anIcon = iconCache.getIcon("nonsense")
assert isinstance(anIcon, QIcon)
assert anIcon.isNull()
anIcon = theIcons.getIcon("novelwriter")
anIcon = iconCache.getIcon("novelwriter")
assert isinstance(anIcon, QIcon)
assert not anIcon.isNull()
# Check return empty icon if file not found
theIcons.ICON_KEYS.add("testicon3")
anIcon = theIcons.getIcon("testicon3")
iconCache.ICON_KEYS.add("testicon3")
anIcon = iconCache.getIcon("testicon3")
assert isinstance(anIcon, QIcon)
assert anIcon.isNull()