Files
novelWriter/tests/test_gui/test_gui_docviewer.py
T
Veronica Berglyd Olsen 5bdbd7c195 Update linting for tests
2025-08-27 21:04:32 +02:00

324 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
novelWriter Main GUI Viewer 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 <https://www.gnu.org/licenses/>.
""" # noqa
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QUrl
from PyQt6.QtGui import (
QAction, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent,
QMouseEvent, QTextCursor
)
from PyQt6.QtWidgets import QApplication, QMenu, QTextBrowser
from novelwriter import CONFIG, SHARED
from novelwriter.common import decodeMimeHandles
from novelwriter.enum import nwChange, nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle
from tests.mocked import causeException
from tests.tools import C, buildTestProject
@pytest.mark.gui
def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
"""Test the document viewer."""
# Open project
assert nwGUI.openProject(prjLipsum)
docEditor = nwGUI.docEditor
docViewer = nwGUI.docViewer
# Select a document in the project tree
nwGUI.projView.setSelectedHandle("88243afbe5ed8")
nwGUI.openDocument("88243afbe5ed8")
# Can only open a document
assert docViewer.loadText("b3643d0f92e32") is False
# Middle-click the selected item
index = SHARED.project.tree.model.indexFromHandle("88243afbe5ed8")
rect = nwGUI.projView.projTree.visualRect(index)
qtbot.mouseClick(nwGUI.projView.projTree.viewport(), QtMouseMiddle, pos=rect.center())
assert docViewer.docHandle == "88243afbe5ed8"
# Clear selection
nwGUI.projView.projTree.clearSelection()
assert nwGUI.projView.projTree.getSelectedHandle() is None
# Re-select via header click
button = QtMouseLeft
modifier = QtModNone
event = QMouseEvent(QEvent.Type.MouseButtonPress, QPointF(), button, button, modifier)
docViewer.docHeader.mousePressEvent(event)
assert nwGUI.projView.projTree.getSelectedHandle() == "88243afbe5ed8"
# Reload the text
origText = docViewer.toPlainText()
docViewer.setPlainText("Oops, all gone!")
docViewer.docHeader._refreshDocument()
assert docViewer.toPlainText() == origText
# Open in editor
nwGUI.closeDocument()
assert docEditor.docHandle is None
docViewer.docHeader._editDocument()
assert docEditor.docHandle == docViewer.docHandle
# Select word
cursor = docViewer.textCursor()
cursor.setPosition(100)
docViewer.setTextCursor(cursor)
docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
clipboard = QApplication.clipboard()
assert clipboard is not None
clipboard.clear()
# Cut
assert docViewer.docAction(nwDocAction.CUT) is True
assert clipboard.text() == "laoreet"
clipboard.clear()
# Copy
assert docViewer.docAction(nwDocAction.COPY) is True
assert clipboard.text() == "laoreet"
clipboard.clear()
# Select Paragraph
assert docViewer.docAction(nwDocAction.SEL_PARA) is True
cursor = docViewer.textCursor()
assert cursor.selectedText() == (
"Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, "
"eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et "
"mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. "
"Etiam finibus nisi vel mi molestie consectetur."
)
cursor.clearSelection()
docViewer.setTextCursor(cursor)
docViewer._makePosSelection(
QTextCursor.SelectionType.BlockUnderCursor, docViewer.cursorRect().center()
)
cursor = docViewer.textCursor()
assert cursor.selectedText() == (
"Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, "
"eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et "
"mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. "
"Etiam finibus nisi vel mi molestie consectetur."
)
cursor.clearSelection()
docViewer.setTextCursor(cursor)
# Select All
assert docViewer.docAction(nwDocAction.SEL_ALL) is True
cursor = docViewer.textCursor()
assert len(cursor.selectedText()) == 3060
# Other actions
assert docViewer.docAction(nwDocAction.NO_ACTION) is False
# Close document
docViewer.docHeader._closeDocument()
assert docViewer.docHandle is None
# Action on no document
assert docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu
nwGUI.projView.projTree.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.ActionEvent.Trigger)
# Open context menu
menuOpened = False
def mockExec(*a):
nonlocal menuOpened
menuOpened = True
cursor = docViewer.textCursor()
cursor.setPosition(27)
docViewer.setTextCursor(cursor)
docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
with monkeypatch.context() as mp:
mp.setattr(QMenu, "exec", mockExec)
docViewer._openContextMenu(docViewer.cursorRect().center())
assert menuOpened
# Select "Bod" link
cursor = docViewer.textCursor()
cursor.setPosition(27)
docViewer.setTextCursor(cursor)
docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
rect = docViewer.cursorRect()
docViewer._linkClicked(QUrl("#tag_bod"))
assert docViewer.docHandle == "4c4f28287af27"
# Other links should just trigger a navigate call
with qtbot.waitSignal(docViewer.sourceChanged, timeout=1000) as signal:
docViewer._linkClicked(QUrl("#somewhere_else"))
assert signal.args[0].url() == "#somewhere_else"
# Web links should trigger the browser
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
docViewer._linkClicked(QUrl("http://www.example.com"))
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# Click mouse nav buttons
viewport = docViewer.viewport()
qtbot.mouseClick(viewport, Qt.MouseButton.BackButton, pos=rect.center(), delay=100)
assert docViewer.docHandle == "88243afbe5ed8"
qtbot.mouseClick(viewport, Qt.MouseButton.ForwardButton, pos=rect.center(), delay=100)
assert docViewer.docHandle == "4c4f28287af27"
qtbot.mouseClick(viewport, QtMouseLeft, pos=rect.center(), delay=100)
assert docViewer.docHandle == "4c4f28287af27"
# Scroll bar default on empty document
docViewer.clear()
assert docViewer.scrollPosition == 0
docViewer.reloadText()
# Flip some settings
CONFIG.doJustify = True
CONFIG.hideVScroll = True
CONFIG.hideHScroll = True
docViewer.initViewer()
assert docViewer.verticalScrollBar().isVisible() is False
assert docViewer.horizontalScrollBar().isVisible() is False
# Change document title
nwItem = SHARED.project.tree["4c4f28287af27"]
nwItem.setName("Test Title") # type: ignore
assert nwItem.itemName == "Test Title" # type: ignore
docViewer.onProjectItemChanged("4c4f28287af27", nwChange.UPDATE)
assert docViewer.docHeader.itemTitle.text() == "Characters \u203a Test Title"
# Title without full path
CONFIG.showFullPath = False
docViewer.onProjectItemChanged("4c4f28287af27", nwChange.UPDATE)
assert docViewer.docHeader.itemTitle.text() == "Test Title"
CONFIG.showFullPath = True
# Document footer show/hide synopsis
nwGUI.viewDocument("f96ec11c6a3da")
assert len(docViewer.toPlainText()) == 4314
docViewer.docFooter._doToggleSynopsis(False)
assert len(docViewer.toPlainText()) == 4098
# Document footer show/hide comments
nwGUI.viewDocument("846352075de7d")
assert len(docViewer.toPlainText()) == 683
docViewer.docFooter._doToggleComments(False)
assert len(docViewer.toPlainText()) == 634
# Document footer show/hide notes
nwGUI.viewDocument("88d59a277361b")
assert len(docViewer.toPlainText()) == 900
docViewer.docFooter._doToggleNotes(False)
assert len(docViewer.toPlainText()) == 871
# Crash the HTML rendering
with monkeypatch.context() as mp:
mp.setattr(ToQTextDocument, "doConvert", causeException)
assert docViewer.loadText("846352075de7d") is False
assert docViewer.toPlainText() == "An error occurred while generating the preview."
# Call the update theme function
# This only checks that t doesn't fail, functionality tested elsewhere
docViewer.updateTheme()
# qtbot.stop()
@pytest.mark.gui
def testGuiViewer_DragAndDrop(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test drag and drop in the viewer."""
docViewer = nwGUI.docViewer
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hTitlePage) is True
assert nwGUI.viewDocument(C.hTitlePage) is True
assert docViewer.docHandle == C.hTitlePage
middle = docViewer.viewport().rect().center()
action = Qt.DropAction.MoveAction
mouse = Qt.MouseButton.NoButton
model = SHARED.project.tree.model
docMime = model.mimeData([model.indexFromHandle(C.hSceneDoc)])
noneMime = QMimeData()
noneMime.setData("plain/text", b"")
assert decodeMimeHandles(docMime) == [C.hSceneDoc]
# Drag Enter
mockEnter = MagicMock()
docEvent = QDragEnterEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDragEnterEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QTextBrowser, "dragEnterEvent", mockEnter)
# Document Enter
docViewer.dragEnterEvent(docEvent)
assert docEvent.isAccepted() is True
assert mockEnter.call_count == 0
# Regular Enter
docViewer.dragEnterEvent(noneEvent)
assert mockEnter.call_count == 1
# Drag Move
mockMove = MagicMock()
docEvent = QDragMoveEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDragMoveEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QTextBrowser, "dragMoveEvent", mockMove)
# Document Move
docViewer.dragMoveEvent(docEvent)
assert docEvent.isAccepted() is True
assert mockMove.call_count == 0
# Regular Move
docViewer.dragMoveEvent(noneEvent)
assert mockMove.call_count == 1
# Drop
mockDrop = MagicMock()
middle = QPointF(docViewer.viewport().rect().center())
docEvent = QDropEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDropEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QTextBrowser, "dropEvent", mockDrop)
# Document Drop
docViewer.dropEvent(docEvent)
assert mockDrop.call_count == 0
assert docViewer.docHandle == C.hSceneDoc
# Regular Move
docViewer.dropEvent(noneEvent)
assert mockDrop.call_count == 1