"""
novelWriter – Main GUI Outline Class Tester
===========================================
This file is a part of novelWriter
Copyright 2018–2023, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
import time
import pytest
from tools import buildTestProject, writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QAction
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass, nwOutline, nwView
@pytest.mark.gui
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, projPath):
"""Test the outline view."""
# Create a project
buildTestProject(nwGUI, projPath)
nwGUI.rebuildIndex()
nwGUI._changeView(nwView.OUTLINE)
outlineView = nwGUI.outlineView
outlineTree = outlineView.outlineTree
outlineData = outlineView.outlineData
outlineMenu = outlineView.outlineBar.mColumns
# Toggle scrollbars
CONFIG.hideVScroll = True
CONFIG.hideHScroll = True
outlineView.initSettings()
assert outlineTree.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineTree.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineData.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineData.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
CONFIG.hideVScroll = False
CONFIG.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 = SHARED.project.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", "columnState", {}) == {}
# Allow saving header state
outlineTree._lastBuild = time.time()
outlineTree._saveHeaderState()
assert list(pOptions.getValue("GuiOutline", "columnState", {}).keys()) == colNames
assert outlineTree._treeOrder == colItems
assert outlineTree._colWidth == colWidth
assert outlineTree._colHidden == colHidden
# Get default values
columnState = pOptions.getValue("GuiOutline", "columnState", {})
# Add invalid column name
newState = columnState.copy()
newState.update({"blabla": (False, 42)})
pOptions.setValue("GuiOutline", "columnState", newState)
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Invalid column width data
newState = columnState.copy()
newState.update({"TITLE": (False, None)})
pOptions.setValue("GuiOutline", "columnState", newState)
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Invalid column state data
newState = columnState.copy()
newState.update({"TITLE": None})
pOptions.setValue("GuiOutline", "columnState", newState)
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Drop a few columns
newState = columnState.copy()
del newState[nwOutline.CHAR.name]
del newState[nwOutline.WORLD.name]
del newState[nwOutline.LINE.name]
pOptions.setValue("GuiOutline", "columnState", newState)
outlineTree._loadHeaderState()
assert len(outlineTree._treeOrder) == len(colItems)
assert len(outlineTree._colHidden) == len(colHidden)
assert nwOutline.CHAR in outlineTree._treeOrder
assert nwOutline.CHAR in outlineTree._colHidden
assert nwOutline.WORLD in outlineTree._treeOrder
assert nwOutline.WORLD in outlineTree._colHidden
assert nwOutline.LINE in outlineTree._treeOrder
assert nwOutline.LINE in outlineTree._colHidden
# Valid settings
pOptions.setValue("GuiOutline", "columnState", columnState)
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()
hiddenStates = [v[0] for v in pOptions.getValue("GuiOutline", "columnState", {}).values()]
assert len(hiddenStates) == len(columnState)
assert not any(hiddenStates)
# qtbot.stop()
# END Test testGuiOutline_Main
@pytest.mark.gui
def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
"""Test the outline view."""
assert nwGUI.openProject(prjLipsum)
nwGUI.rebuildIndex()
nwGUI._changeView(nwView.OUTLINE)
outlineView = nwGUI.outlineView
outlineBar = outlineView.outlineBar
outlineTree = outlineView.outlineTree
outlineData = outlineView.outlineData
lipHandle = "b3643d0f92e32"
# 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 = SHARED.project.newRoot(nwItemClass.NOVEL)
nwGUI.projView.projTree.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 = SHARED.project.newFile(dTitle, newHandle)
hHash = "#"*hLevel
writeFile(prjLipsum / "content" / f"{aHandle}.nwd", f"{hHash} {dTitle}\n\n")
nwGUI.projView.projTree.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
outlineTree.refreshTree()
selItem = outlineTree.topLevelItem(0)
outlineTree.setCurrentItem(selItem)
assert outlineData.titleLabel.text() == "Title"
assert outlineData.titleValue.text() == "Lorem Ipsum"
assert outlineData.fileValue.text() == "Lorem Ipsum"
assert outlineData.itemValue.text() == "Finished"
assert outlineData.cCValue.text() == "230"
assert outlineData.wCValue.text() == "40"
assert outlineData.pCValue.text() == "3"
# Scene One
selItem = outlineTree.topLevelItem(4)
outlineTree.setCurrentItem(selItem)
tHandle, sTitle = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8"
assert sTitle == "T0001"
assert outlineData.titleLabel.text() == "Scene"
assert outlineData.titleValue.text() == "Scene One"
assert outlineData.fileValue.text() == "Scene One"
assert outlineData.itemValue.text() == "Finished"
# Click POV Link
assert outlineData.povKeyValue.text() == "Bod"
outlineView._tagClicked("Bod")
assert nwGUI.docViewer.docHandle == "4c4f28287af27"
# Scene One, Section Two
selItem = outlineTree.topLevelItem(5)
outlineTree.setCurrentItem(selItem)
tHandle, sTitle = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8"
assert sTitle == "T0002"
assert outlineData.titleLabel.text() == "Section"
assert outlineData.titleValue.text() == "Scene One, Section Two"
assert outlineData.fileValue.text() == "Scene One"
assert outlineData.itemValue.text() == "Finished"
outlineTree._treeDoubleClick(selItem, 0)
assert nwGUI.docEditor.docHandle == "88243afbe5ed8"
# qtbot.stop()
# END Test testGuiOutline_Content