Update tests
This commit is contained in:
@@ -189,7 +189,7 @@ class NExpandablePanelGroup(QSplitter):
|
||||
def _panelToggled(self, state: bool) -> None:
|
||||
"""Collapse or expand a panel when its state changes."""
|
||||
panel = self.sender()
|
||||
if isinstance(panel, NExpandablePanel):
|
||||
if isinstance(panel, NExpandablePanel): # pragma: no branch
|
||||
if state:
|
||||
self._expand(panel)
|
||||
else:
|
||||
@@ -225,5 +225,5 @@ class NExpandablePanelGroup(QSplitter):
|
||||
before = self.widget(i - 1)
|
||||
after = self.widget(i)
|
||||
locked = any(isinstance(w, NExpandablePanel) and not w.isExpanded() for w in (before, after))
|
||||
if isinstance(handle := self.handle(i), NSplitterHandle):
|
||||
if isinstance(handle := self.handle(i), NSplitterHandle): # pragma: no branch
|
||||
handle.setResizable(not locked)
|
||||
|
||||
@@ -82,13 +82,13 @@ class NToolDialog(NDialog):
|
||||
def __init__(self, parent: GuiMain) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setModal(False)
|
||||
if CONFIG.osDarwin:
|
||||
if CONFIG.osDarwin: # pragma: no cover
|
||||
self.setWindowFlag(Qt.WindowType.Tool)
|
||||
|
||||
def activateDialog(self) -> None:
|
||||
"""Activate dialog on various operating systems."""
|
||||
self.show()
|
||||
if CONFIG.osWindows:
|
||||
if CONFIG.osWindows: # pragma: no cover
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
QApplication.processEvents()
|
||||
@@ -104,7 +104,7 @@ class NNonBlockingDialog(NDialog):
|
||||
def activateDialog(self) -> None:
|
||||
"""Activate dialog on various operating systems."""
|
||||
self.show()
|
||||
if CONFIG.osWindows:
|
||||
if CONFIG.osWindows: # pragma: no cover
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
QApplication.processEvents()
|
||||
|
||||
31
run_tests.py
31
run_tests.py
@@ -11,34 +11,6 @@ import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _genCoverageConfig(path: Path) -> None:
|
||||
"""Generate a config file for coverage.py."""
|
||||
exclude = [] # ["if TYPE_CHECKING:"]
|
||||
if sys.platform != "linux":
|
||||
exclude.append(r'\s*(if|elif) sys\.platform == "linux":')
|
||||
exclude.append(r"\s*(if|elif) (CONFIG|self)\.osLinux")
|
||||
if sys.platform != "darwin":
|
||||
exclude.append(r'\s*(if|elif) sys\.platform == "darwin":')
|
||||
exclude.append(r"\s*(if|elif) (CONFIG|self)\.osDarwin")
|
||||
if sys.platform != "win32":
|
||||
exclude.append(r'\s*(if|elif) sys\.platform == "win32":')
|
||||
exclude.append(r"\s*(if|elif) (CONFIG|self)\.osWindows")
|
||||
|
||||
config = [
|
||||
"[run]",
|
||||
"branch = true",
|
||||
"source = novelwriter",
|
||||
"",
|
||||
"[report]",
|
||||
"precision = 2",
|
||||
"exclude_also =",
|
||||
]
|
||||
config.extend(f" {ex}" for ex in exclude)
|
||||
|
||||
path.write_text("\n".join(config), encoding="utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Parse command line options and run the commands."""
|
||||
parser = argparse.ArgumentParser(usage="run_tests.py [--flags]")
|
||||
@@ -46,7 +18,6 @@ if __name__ == "__main__":
|
||||
parser.add_argument("-r", action="store_true", help="Generate reports")
|
||||
parser.add_argument("-t", action="store_true", help="Generate terminal report")
|
||||
parser.add_argument("-u", action="store_true", help="Generate uncovered terminal report")
|
||||
parser.add_argument("-x", action="store_true", help="Ignore OS-specific tests in reports")
|
||||
parser.add_argument("-lf", action="store_true", help="Re-run failed tests")
|
||||
parser.add_argument("-sw", action="store_true", help="Run tests stepwise")
|
||||
parser.add_argument("-m", help="Test modules", metavar="MARKEXPR")
|
||||
@@ -59,8 +30,6 @@ if __name__ == "__main__":
|
||||
tmpConf = Path(__file__).parent / ".coveragerc"
|
||||
|
||||
if args.r or args.t or args.u:
|
||||
if args.x:
|
||||
_genCoverageConfig(tmpConf)
|
||||
cmd = ["coverage", "run"]
|
||||
if args.lf or args.sw:
|
||||
cmd += ["--append"]
|
||||
|
||||
@@ -23,9 +23,11 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt6.QtWidgets import QLabel, QVBoxLayout
|
||||
from PyQt6.QtGui import QPalette
|
||||
from PyQt6.QtWidgets import QWIDGETSIZE_MAX, QLabel, QVBoxLayout
|
||||
|
||||
from novelwriter.extensions.expandpanel import NExpandablePanel
|
||||
from novelwriter.extensions.expandpanel import NExpandablePanel, NExpandablePanelGroup
|
||||
from novelwriter.extensions.modified import NSplitterHandle
|
||||
from novelwriter.types import QtMouseLeft
|
||||
|
||||
from tests.helpers import SimpleDialog
|
||||
@@ -59,18 +61,27 @@ def testNExpandablePanel_Main(qtbot, mockGUI):
|
||||
panel.setContentLayout(content)
|
||||
assert panel._ep_widget.layout() is content
|
||||
|
||||
# Click the toggle button to collapse the panel
|
||||
# Set the header background role
|
||||
panel.setHeaderBackgroundRole(QPalette.ColorRole.Highlight)
|
||||
assert panel._ep_headerBox.backgroundRole() == QPalette.ColorRole.Highlight
|
||||
assert panel._ep_headerBox.autoFillBackground() is True
|
||||
|
||||
# Click the toggle button to collapse the panel, which locks its
|
||||
# height to that of the header
|
||||
qtbot.mouseClick(panel._ep_toggle, QtMouseLeft)
|
||||
assert panel.isExpanded() is False
|
||||
assert panel._ep_widget.isVisible() is False
|
||||
assert panel._ep_toggle.isChecked() is False
|
||||
assert panel.maximumHeight() == panel.minimumSizeHint().height()
|
||||
assert changes == [False]
|
||||
|
||||
# Click the title label to expand the panel again
|
||||
# Click the title label to expand the panel again, which lifts the
|
||||
# height restriction
|
||||
qtbot.mouseClick(panel._ep_label, QtMouseLeft)
|
||||
assert panel.isExpanded() is True
|
||||
assert panel._ep_widget.isVisible() is True
|
||||
assert panel._ep_toggle.isChecked() is True
|
||||
assert panel.maximumHeight() == QWIDGETSIZE_MAX
|
||||
assert changes == [False, True]
|
||||
|
||||
# Set the expanded state directly, which must also update the toggle
|
||||
@@ -101,3 +112,82 @@ def testNExpandablePanel_Main(qtbot, mockGUI):
|
||||
|
||||
# Update the theme
|
||||
panel.updateTheme()
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testNExpandablePanelGroup_Main(qtbot, mockGUI):
|
||||
"""Test the NExpandablePanelGroup class."""
|
||||
dialog = SimpleDialog()
|
||||
group = NExpandablePanelGroup(dialog)
|
||||
dialog.addWidget(group)
|
||||
|
||||
panel = NExpandablePanel()
|
||||
panel.setTitle("Panel")
|
||||
content = QVBoxLayout()
|
||||
content.addWidget(QLabel("Some content that needs space", panel))
|
||||
panel.setContentLayout(content)
|
||||
|
||||
other = QLabel("Other pane")
|
||||
|
||||
group.addWidget(panel)
|
||||
group.addWidget(other)
|
||||
dialog.resize(400, 600)
|
||||
dialog.show()
|
||||
qtbot.addWidget(dialog)
|
||||
qtbot.wait(10)
|
||||
|
||||
# Adding a panel connects its state change, tracks its size, and
|
||||
# applies the group's header background role
|
||||
assert panel in group._expandedSizes
|
||||
assert panel._ep_headerBox.backgroundRole() == QPalette.ColorRole.AlternateBase
|
||||
assert panel._ep_headerBox.autoFillBackground() is True
|
||||
|
||||
# Both panes start expanded, so the handle between them is resizable
|
||||
handle = group.handle(1)
|
||||
assert isinstance(handle, NSplitterHandle)
|
||||
assert handle._resizable is True
|
||||
|
||||
# A size of zero or less does not overwrite the remembered expanded
|
||||
# size, since it does not represent a meaningful size to restore to
|
||||
beforeSize = group._expandedSizes[panel]
|
||||
group.setPanelSizes([0, 200])
|
||||
assert group._expandedSizes[panel] == beforeSize
|
||||
|
||||
# Setting sizes is remembered as the panel's expanded size. The actual
|
||||
# on-screen pixel sizes are not checked here, as QSplitter distributes
|
||||
# them proportionally to fit the available space, not verbatim.
|
||||
group.setPanelSizes([150, 200])
|
||||
assert group._expandedSizes[panel] == 150
|
||||
qtbot.wait(10)
|
||||
expandedHeight = group.sizes()[0]
|
||||
|
||||
# Collapsing the panel locks it to its header height, remembers the
|
||||
# actual size it had, and disables the now-useless handle
|
||||
panel.setExpanded(False)
|
||||
qtbot.wait(10)
|
||||
collapsedHeight = panel.minimumSizeHint().height()
|
||||
assert group.sizes()[0] == collapsedHeight
|
||||
assert group.panelSizes()[0] == expandedHeight
|
||||
assert handle._resizable is False
|
||||
|
||||
# Expanding it again grows the panel back out and re-enables the handle.
|
||||
# The exact pixel value may differ from what was remembered, since the
|
||||
# other pane may since have grown to fill the space that was freed up,
|
||||
# leaving less than requested for the splitter to redistribute back
|
||||
panel.setExpanded(True)
|
||||
qtbot.wait(10)
|
||||
assert group.sizes()[0] > collapsedHeight
|
||||
assert handle._resizable is True
|
||||
|
||||
# Adding a panel that starts out collapsed keeps the handle disabled
|
||||
# and does not reserve space it cannot actually use
|
||||
collapsed = NExpandablePanel()
|
||||
collapsed.setTitle("Collapsed")
|
||||
collapsed.setExpanded(False)
|
||||
group.addWidget(collapsed)
|
||||
qtbot.wait(10)
|
||||
|
||||
lastHandle = group.handle(2)
|
||||
assert isinstance(lastHandle, NSplitterHandle)
|
||||
assert lastHandle._resizable is False
|
||||
assert group.sizes()[2] == collapsed.minimumSizeHint().height()
|
||||
|
||||
@@ -27,7 +27,7 @@ import pytest
|
||||
|
||||
from PyQt6.QtCore import QEvent, QPoint, QPointF, QSize, Qt
|
||||
from PyQt6.QtGui import QFont, QKeyEvent, QMouseEvent, QStandardItem, QStandardItemModel, QWheelEvent
|
||||
from PyQt6.QtWidgets import QFontDialog, QWidget
|
||||
from PyQt6.QtWidgets import QFontDialog, QSplitter, QWidget
|
||||
|
||||
from novelwriter.extensions.modified import (
|
||||
NClickableLabel,
|
||||
@@ -39,6 +39,7 @@ from novelwriter.extensions.modified import (
|
||||
NIconToolButton,
|
||||
NNonBlockingDialog,
|
||||
NSpinBox,
|
||||
NSplitterHandle,
|
||||
NToolDialog,
|
||||
NTreeView,
|
||||
)
|
||||
@@ -290,3 +291,66 @@ def testNToolButtons_Main(qtbot, mockGUI):
|
||||
assert button1.isCheckable() is True
|
||||
button1.setCheckable(False)
|
||||
assert button1.isCheckable() is False
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testNSplitterHandle_Main(qtbot):
|
||||
"""Test the NSplitterHandle class."""
|
||||
splitter = QSplitter(Qt.Orientation.Vertical)
|
||||
handle = NSplitterHandle(Qt.Orientation.Vertical, splitter)
|
||||
dialog = SimpleDialog(splitter)
|
||||
dialog.show()
|
||||
qtbot.addWidget(dialog)
|
||||
qtbot.wait(10)
|
||||
|
||||
# Starts out resizable, with the vertical split cursor, and inactive
|
||||
assert handle._resizable is True
|
||||
assert handle.cursor().shape() == Qt.CursorShape.SplitVCursor
|
||||
assert handle._active is False
|
||||
|
||||
# Paint while inactive draws nothing but must not fail
|
||||
handle.repaint()
|
||||
|
||||
# Hovering activates the highlight, and leaving drops it again
|
||||
handle.event(QEvent(QEvent.Type.HoverEnter))
|
||||
assert handle._active is True
|
||||
handle.event(QEvent(QEvent.Type.HoverLeave))
|
||||
assert handle._active is False
|
||||
|
||||
# A mouse press keeps it highlighted while dragging, and the highlight
|
||||
# is now actually painted
|
||||
position = QPointF(handle.rect().center())
|
||||
press = QMouseEvent(QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModNone)
|
||||
handle.mousePressEvent(press)
|
||||
assert handle._active is True
|
||||
handle.repaint()
|
||||
|
||||
# On release, the highlight follows whether the cursor is still over it
|
||||
release = QMouseEvent(QEvent.Type.MouseButtonRelease, position, QtMouseLeft, QtMouseLeft, QtModNone)
|
||||
handle.mouseReleaseEvent(release)
|
||||
assert handle._active is handle.underMouse()
|
||||
|
||||
# Marking it non-resizable drops any active highlight, switches to a
|
||||
# plain arrow cursor, and suppresses further hover/press highlighting
|
||||
handle.setResizable(False)
|
||||
assert handle._resizable is False
|
||||
assert handle._active is False
|
||||
assert handle.cursor().shape() == Qt.CursorShape.ArrowCursor
|
||||
|
||||
handle.event(QEvent(QEvent.Type.HoverEnter))
|
||||
assert handle._active is False
|
||||
handle.mousePressEvent(press)
|
||||
assert handle._active is False
|
||||
|
||||
# Marking it resizable again restores the highlight cursor
|
||||
handle.setResizable(True)
|
||||
assert handle._resizable is True
|
||||
assert handle.cursor().shape() == Qt.CursorShape.SplitVCursor
|
||||
|
||||
# The horizontal orientation uses the horizontal split cursor instead
|
||||
hSplitter = QSplitter(Qt.Orientation.Horizontal)
|
||||
hHandle = NSplitterHandle(Qt.Orientation.Horizontal, hSplitter)
|
||||
assert hHandle.cursor().shape() == Qt.CursorShape.SplitHCursor
|
||||
hHandle.setResizable(False)
|
||||
hHandle.setResizable(True)
|
||||
assert hHandle.cursor().shape() == Qt.CursorShape.SplitHCursor
|
||||
|
||||
Reference in New Issue
Block a user