mirror of
https://github.com/pyapp-kit/superqt.git
synced 2025-12-16 11:10:06 +01:00
move to src layout (#32)
* move to src layout * fix manifest and version * fix test structure * undo * undo * undo change * remove pyargs * waitsignal * update label test * soften eliding test * another fix * update again * more fixes * more skips * stupid fixes
This commit is contained in:
70
tests/test_eliding_label.py
Normal file
70
tests/test_eliding_label.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import platform
|
||||
|
||||
from superqt import QElidingLabel
|
||||
from superqt.qtcompat.QtCore import QSize, Qt
|
||||
from superqt.qtcompat.QtGui import QResizeEvent
|
||||
|
||||
TEXT = (
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
|
||||
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad "
|
||||
"minim ven iam, quis nostrud exercitation ullamco laborisnisi ut aliquip "
|
||||
"ex ea commodo consequat. Duis aute irure dolor inreprehenderit in voluptate "
|
||||
"velit esse cillum dolore eu fugiat nullapariatur."
|
||||
)
|
||||
ELLIPSIS = "…"
|
||||
|
||||
|
||||
def test_eliding_label(qtbot):
|
||||
wdg = QElidingLabel(TEXT)
|
||||
qtbot.addWidget(wdg)
|
||||
assert wdg._elidedText().endswith(ELLIPSIS)
|
||||
oldsize = wdg.size()
|
||||
newsize = QSize(200, 20)
|
||||
wdg.resize(newsize)
|
||||
wdg.resizeEvent(QResizeEvent(oldsize, newsize)) # for test coverage
|
||||
assert wdg.text() == TEXT
|
||||
|
||||
|
||||
def test_wrapped_eliding_label(qtbot):
|
||||
wdg = QElidingLabel(TEXT)
|
||||
qtbot.addWidget(wdg)
|
||||
assert not wdg.wordWrap()
|
||||
assert 630 < wdg.sizeHint().width() < 638
|
||||
assert wdg._elidedText().endswith("…")
|
||||
wdg.resize(QSize(200, 100))
|
||||
assert wdg.text() == TEXT
|
||||
assert wdg._elidedText().endswith("…")
|
||||
wdg.setWordWrap(True)
|
||||
assert wdg.wordWrap()
|
||||
assert wdg.text() == TEXT
|
||||
assert wdg._elidedText().endswith("…")
|
||||
# just empirically from CI ... stupid
|
||||
if platform.system() == "Linux":
|
||||
assert wdg.sizeHint() in (QSize(200, 198), QSize(200, 154))
|
||||
elif platform.system() == "Windows":
|
||||
assert wdg.sizeHint() in (QSize(200, 160), QSize(200, 118))
|
||||
elif platform.system() == "Darwin":
|
||||
assert wdg.sizeHint() == QSize(200, 176)
|
||||
# TODO: figure out how to test these on all platforms on CI
|
||||
wdg.resize(wdg.sizeHint())
|
||||
assert wdg._elidedText() == TEXT
|
||||
|
||||
|
||||
def test_shorter_eliding_label(qtbot):
|
||||
short = "asd a ads sd flksdf dsf lksfj sd lsdjf sd lsdfk sdlkfj s"
|
||||
wdg = QElidingLabel()
|
||||
qtbot.addWidget(wdg)
|
||||
wdg.setText(short)
|
||||
assert not wdg._elidedText().endswith(ELLIPSIS)
|
||||
wdg.resize(100, 20)
|
||||
assert wdg._elidedText().endswith(ELLIPSIS)
|
||||
wdg.setElideMode(Qt.TextElideMode.ElideLeft)
|
||||
assert wdg._elidedText().startswith(ELLIPSIS)
|
||||
assert wdg.elideMode() == Qt.TextElideMode.ElideLeft
|
||||
|
||||
|
||||
def test_wrap_text():
|
||||
wrap = QElidingLabel.wrapText(TEXT, 200)
|
||||
assert isinstance(wrap, list)
|
||||
assert all(isinstance(x, str) for x in wrap)
|
||||
assert 9 <= len(wrap) <= 13
|
||||
212
tests/test_ensure_thread.py
Normal file
212
tests/test_ensure_thread.py
Normal file
@@ -0,0 +1,212 @@
|
||||
import inspect
|
||||
import time
|
||||
from concurrent.futures import Future, TimeoutError
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt.qtcompat.QtCore import QCoreApplication, QObject, QThread, Signal
|
||||
from superqt.utils import ensure_main_thread, ensure_object_thread
|
||||
|
||||
|
||||
class SampleObject(QObject):
|
||||
assigment_done = Signal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.main_thread_res = {}
|
||||
self.object_thread_res = {}
|
||||
self.main_thread_prop_val = None
|
||||
self.sample_thread_prop_val = None
|
||||
|
||||
def long_wait(self):
|
||||
time.sleep(1)
|
||||
|
||||
@property
|
||||
def sample_main_thread_property(self):
|
||||
return self.main_thread_prop_val
|
||||
|
||||
@sample_main_thread_property.setter # type: ignore
|
||||
@ensure_main_thread()
|
||||
def sample_main_thread_property(self, value):
|
||||
if QThread.currentThread() is not QCoreApplication.instance().thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
self.main_thread_prop_val = value
|
||||
self.assigment_done.emit()
|
||||
|
||||
@property
|
||||
def sample_object_thread_property(self):
|
||||
return self.sample_thread_prop_val
|
||||
|
||||
@sample_object_thread_property.setter # type: ignore
|
||||
@ensure_object_thread()
|
||||
def sample_object_thread_property(self, value):
|
||||
if QThread.currentThread() is not self.thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
self.sample_thread_prop_val = value
|
||||
self.assigment_done.emit()
|
||||
|
||||
@ensure_main_thread
|
||||
def check_main_thread(self, a, *, b=1):
|
||||
if QThread.currentThread() is not QCoreApplication.instance().thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
self.main_thread_res = {"a": a, "b": b}
|
||||
self.assigment_done.emit()
|
||||
|
||||
@ensure_object_thread
|
||||
def check_object_thread(self, a, *, b=1):
|
||||
if QThread.currentThread() is not self.thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
self.object_thread_res = {"a": a, "b": b}
|
||||
self.assigment_done.emit()
|
||||
|
||||
@ensure_object_thread(await_return=True)
|
||||
def check_object_thread_return(self, a):
|
||||
if QThread.currentThread() is not self.thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
return a * 7
|
||||
|
||||
@ensure_object_thread(await_return=True, timeout=200)
|
||||
def check_object_thread_return_timeout(self, a):
|
||||
if QThread.currentThread() is not self.thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
time.sleep(1)
|
||||
return a * 7
|
||||
|
||||
@ensure_object_thread(await_return=False)
|
||||
def check_object_thread_return_future(self, a: int):
|
||||
"""sample docstring"""
|
||||
if QThread.currentThread() is not self.thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
time.sleep(0.4)
|
||||
return a * 7
|
||||
|
||||
@ensure_main_thread(await_return=True)
|
||||
def check_main_thread_return(self, a):
|
||||
if QThread.currentThread() is not QCoreApplication.instance().thread():
|
||||
raise RuntimeError("Wrong thread")
|
||||
return a * 8
|
||||
|
||||
|
||||
class LocalThread(QThread):
|
||||
def __init__(self, ob):
|
||||
super().__init__()
|
||||
self.ob = ob
|
||||
|
||||
def run(self):
|
||||
assert QThread.currentThread() is not QCoreApplication.instance().thread()
|
||||
self.ob.check_main_thread(5, b=8)
|
||||
self.ob.main_thread_prop_val = "text2"
|
||||
|
||||
|
||||
class LocalThread2(QThread):
|
||||
def __init__(self, ob):
|
||||
super().__init__()
|
||||
self.ob = ob
|
||||
self.executed = False
|
||||
|
||||
def run(self):
|
||||
assert QThread.currentThread() is not QCoreApplication.instance().thread()
|
||||
assert self.ob.check_main_thread_return(5) == 40
|
||||
self.executed = True
|
||||
|
||||
|
||||
def test_only_main_thread(qapp):
|
||||
ob = SampleObject()
|
||||
ob.check_main_thread(1, b=3)
|
||||
assert ob.main_thread_res == {"a": 1, "b": 3}
|
||||
ob.check_object_thread(2, b=4)
|
||||
assert ob.object_thread_res == {"a": 2, "b": 4}
|
||||
ob.sample_main_thread_property = 5
|
||||
assert ob.sample_main_thread_property == 5
|
||||
ob.sample_object_thread_property = 7
|
||||
assert ob.sample_object_thread_property == 7
|
||||
|
||||
|
||||
def test_object_thread(qtbot):
|
||||
ob = SampleObject()
|
||||
thread = QThread()
|
||||
thread.start()
|
||||
ob.moveToThread(thread)
|
||||
with qtbot.waitSignal(ob.assigment_done):
|
||||
ob.check_object_thread(2, b=4)
|
||||
assert ob.object_thread_res == {"a": 2, "b": 4}
|
||||
|
||||
with qtbot.waitSignal(ob.assigment_done):
|
||||
ob.sample_object_thread_property = "text"
|
||||
|
||||
assert ob.sample_object_thread_property == "text"
|
||||
assert ob.thread() is thread
|
||||
with qtbot.waitSignal(thread.finished):
|
||||
thread.exit(0)
|
||||
|
||||
|
||||
def test_main_thread(qtbot):
|
||||
ob = SampleObject()
|
||||
t = LocalThread(ob)
|
||||
with qtbot.waitSignal(t.finished):
|
||||
t.start()
|
||||
|
||||
assert ob.main_thread_res == {"a": 5, "b": 8}
|
||||
assert ob.sample_main_thread_property == "text2"
|
||||
|
||||
|
||||
def test_object_thread_return(qtbot):
|
||||
ob = SampleObject()
|
||||
thread = QThread()
|
||||
thread.start()
|
||||
ob.moveToThread(thread)
|
||||
assert ob.check_object_thread_return(2) == 14
|
||||
assert ob.thread() is thread
|
||||
with qtbot.waitSignal(thread.finished):
|
||||
thread.exit(0)
|
||||
|
||||
|
||||
def test_object_thread_return_timeout(qtbot):
|
||||
ob = SampleObject()
|
||||
thread = QThread()
|
||||
thread.start()
|
||||
ob.moveToThread(thread)
|
||||
with pytest.raises(TimeoutError):
|
||||
ob.check_object_thread_return_timeout(2)
|
||||
with qtbot.waitSignal(thread.finished):
|
||||
thread.exit(0)
|
||||
|
||||
|
||||
def test_object_thread_return_future(qtbot):
|
||||
ob = SampleObject()
|
||||
thread = QThread()
|
||||
thread.start()
|
||||
ob.moveToThread(thread)
|
||||
future = ob.check_object_thread_return_future(2)
|
||||
assert isinstance(future, Future)
|
||||
assert future.result() == 14
|
||||
with qtbot.waitSignal(thread.finished):
|
||||
thread.exit(0)
|
||||
|
||||
|
||||
def test_main_thread_return(qtbot):
|
||||
ob = SampleObject()
|
||||
t = LocalThread2(ob)
|
||||
with qtbot.wait_signal(t.finished):
|
||||
t.start()
|
||||
assert t.executed
|
||||
|
||||
|
||||
def test_names(qapp):
|
||||
ob = SampleObject()
|
||||
assert ob.check_object_thread.__name__ == "check_object_thread"
|
||||
assert ob.check_object_thread_return.__name__ == "check_object_thread_return"
|
||||
assert (
|
||||
ob.check_object_thread_return_timeout.__name__
|
||||
== "check_object_thread_return_timeout"
|
||||
)
|
||||
assert (
|
||||
ob.check_object_thread_return_future.__name__
|
||||
== "check_object_thread_return_future"
|
||||
)
|
||||
assert ob.check_object_thread_return_future.__doc__ == "sample docstring"
|
||||
signature = inspect.signature(ob.check_object_thread_return_future)
|
||||
assert len(signature.parameters) == 1
|
||||
assert list(signature.parameters.values())[0].name == "a"
|
||||
assert list(signature.parameters.values())[0].annotation == int
|
||||
assert ob.check_main_thread_return.__name__ == "check_main_thread_return"
|
||||
129
tests/test_enum_comb_box.py
Normal file
129
tests/test_enum_comb_box.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from enum import Enum
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt.combobox import QEnumComboBox
|
||||
from superqt.combobox._enum_combobox import NONE_STRING
|
||||
|
||||
|
||||
class Enum1(Enum):
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
|
||||
|
||||
class Enum2(Enum):
|
||||
d = 1
|
||||
e = 2
|
||||
f = 3
|
||||
g = 4
|
||||
|
||||
|
||||
class Enum3(Enum):
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
|
||||
def __str__(self):
|
||||
return self.name + "1"
|
||||
|
||||
|
||||
class Enum4(Enum):
|
||||
a_1 = 1
|
||||
b_2 = 2
|
||||
c_3 = 3
|
||||
|
||||
|
||||
def test_simple_create(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum1)
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.count() == 3
|
||||
assert [enum.itemText(i) for i in range(enum.count())] == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_simple_create2(qtbot):
|
||||
enum = QEnumComboBox()
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.count() == 0
|
||||
enum.setEnumClass(Enum1)
|
||||
assert enum.count() == 3
|
||||
assert [enum.itemText(i) for i in range(enum.count())] == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_replace(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum1)
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.count() == 3
|
||||
assert enum.enumClass() == Enum1
|
||||
assert isinstance(enum.currentEnum(), Enum1)
|
||||
enum.setEnumClass(Enum2)
|
||||
assert enum.enumClass() == Enum2
|
||||
assert isinstance(enum.currentEnum(), Enum2)
|
||||
assert enum.count() == 4
|
||||
assert [enum.itemText(i) for i in range(enum.count())] == ["d", "e", "f", "g"]
|
||||
|
||||
|
||||
def test_str_replace(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum3)
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.count() == 3
|
||||
assert [enum.itemText(i) for i in range(enum.count())] == ["a1", "b1", "c1"]
|
||||
|
||||
|
||||
def test_underscore_replace(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum4)
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.count() == 3
|
||||
assert [enum.itemText(i) for i in range(enum.count())] == ["a 1", "b 2", "c 3"]
|
||||
|
||||
|
||||
def test_change_value(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum1)
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.currentEnum() == Enum1.a
|
||||
with qtbot.waitSignal(
|
||||
enum.currentEnumChanged, check_params_cb=lambda x: isinstance(x, Enum)
|
||||
):
|
||||
enum.setCurrentEnum(Enum1.c)
|
||||
assert enum.currentEnum() == Enum1.c
|
||||
|
||||
|
||||
def test_no_enum(qtbot):
|
||||
enum = QEnumComboBox()
|
||||
assert enum.enumClass() is None
|
||||
qtbot.addWidget(enum)
|
||||
assert enum.currentEnum() is None
|
||||
|
||||
|
||||
def test_prohibited_methods(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum1)
|
||||
qtbot.addWidget(enum)
|
||||
with pytest.raises(RuntimeError):
|
||||
enum.addItem("aaa")
|
||||
with pytest.raises(RuntimeError):
|
||||
enum.addItems(["aaa", "bbb"])
|
||||
with pytest.raises(RuntimeError):
|
||||
enum.insertItem(0, "aaa")
|
||||
with pytest.raises(RuntimeError):
|
||||
enum.insertItems(0, ["aaa", "bbb"])
|
||||
assert enum.count() == 3
|
||||
|
||||
|
||||
def test_optional(qtbot):
|
||||
enum = QEnumComboBox(enum_class=Enum1, allow_none=True)
|
||||
qtbot.addWidget(enum)
|
||||
assert [enum.itemText(i) for i in range(enum.count())] == [
|
||||
NONE_STRING,
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
]
|
||||
assert enum.currentText() == NONE_STRING
|
||||
assert enum.currentEnum() is None
|
||||
enum.setCurrentEnum(Enum1.a)
|
||||
assert enum.currentText() == "a"
|
||||
assert enum.currentEnum() == Enum1.a
|
||||
assert enum.enumClass() is Enum1
|
||||
enum.setCurrentEnum(None)
|
||||
assert enum.currentText() == NONE_STRING
|
||||
assert enum.currentEnum() is None
|
||||
73
tests/test_large_int_spinbox.py
Normal file
73
tests/test_large_int_spinbox.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from superqt.qtcompat.QtCore import Qt
|
||||
from superqt.spinbox import QLargeIntSpinBox
|
||||
|
||||
|
||||
def test_large_spinbox(qtbot):
|
||||
sb = QLargeIntSpinBox()
|
||||
qtbot.addWidget(sb)
|
||||
|
||||
for e in range(2, 100, 2):
|
||||
sb.setMaximum(10 ** e + 2)
|
||||
with qtbot.waitSignal(sb.valueChanged) as sgnl:
|
||||
sb.setValue(10 ** e)
|
||||
assert sgnl.args == [10 ** e]
|
||||
assert sb.value() == 10 ** e
|
||||
|
||||
sb.setMinimum(-(10 ** e) - 2)
|
||||
|
||||
with qtbot.waitSignal(sb.valueChanged) as sgnl:
|
||||
sb.setValue(-(10 ** e))
|
||||
assert sgnl.args == [-(10 ** e)]
|
||||
assert sb.value() == -(10 ** e)
|
||||
|
||||
|
||||
def test_large_spinbox_type(qtbot):
|
||||
sb = QLargeIntSpinBox()
|
||||
qtbot.addWidget(sb)
|
||||
|
||||
assert isinstance(sb.value(), int)
|
||||
|
||||
sb.setValue(1.1)
|
||||
assert isinstance(sb.value(), int)
|
||||
assert sb.value() == 1
|
||||
|
||||
sb.setValue(1.9)
|
||||
assert isinstance(sb.value(), int)
|
||||
assert sb.value() == 1
|
||||
|
||||
|
||||
def test_large_spinbox_signals(qtbot):
|
||||
sb = QLargeIntSpinBox()
|
||||
qtbot.addWidget(sb)
|
||||
|
||||
with qtbot.waitSignal(sb.valueChanged) as sgnl:
|
||||
sb.setValue(200)
|
||||
assert sgnl.args == [200]
|
||||
|
||||
with qtbot.waitSignal(sb.textChanged) as sgnl:
|
||||
sb.setValue(240)
|
||||
assert sgnl.args == ["240"]
|
||||
|
||||
|
||||
def test_keyboard_tracking(qtbot):
|
||||
sb = QLargeIntSpinBox()
|
||||
qtbot.addWidget(sb)
|
||||
|
||||
assert sb.value() == 0
|
||||
sb.setKeyboardTracking(False)
|
||||
with qtbot.assertNotEmitted(sb.valueChanged):
|
||||
sb.lineEdit().setText("20")
|
||||
assert sb.lineEdit().text() == "20"
|
||||
assert sb.value() == 0
|
||||
assert sb._pending_emit is True
|
||||
|
||||
with qtbot.waitSignal(sb.valueChanged) as sgnl:
|
||||
qtbot.keyPress(sb, Qt.Key_Enter)
|
||||
assert sgnl.args == [20]
|
||||
assert sb._pending_emit is False
|
||||
|
||||
sb.setKeyboardTracking(True)
|
||||
with qtbot.waitSignal(sb.valueChanged) as sgnl:
|
||||
sb.lineEdit().setText("25")
|
||||
assert sb._pending_emit is False
|
||||
assert sgnl.args == [25]
|
||||
35
tests/test_qmessage_handler.py
Normal file
35
tests/test_qmessage_handler.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import logging
|
||||
|
||||
from superqt import QMessageHandler
|
||||
from superqt.qtcompat import QtCore
|
||||
|
||||
|
||||
def test_message_handler():
|
||||
with QMessageHandler() as mh:
|
||||
QtCore.qDebug("debug")
|
||||
QtCore.qWarning("warning")
|
||||
QtCore.qCritical("critical")
|
||||
|
||||
assert len(mh.records) == 3
|
||||
assert mh.records[0].level == logging.DEBUG
|
||||
assert mh.records[1].level == logging.WARNING
|
||||
assert mh.records[2].level == logging.CRITICAL
|
||||
|
||||
assert "3 records" in repr(mh)
|
||||
|
||||
|
||||
def test_message_handler_with_logger(caplog):
|
||||
logger = logging.getLogger("test_logger")
|
||||
caplog.set_level(logging.DEBUG, logger="test_logger")
|
||||
with QMessageHandler(logger):
|
||||
QtCore.qDebug("debug")
|
||||
QtCore.qWarning("warning")
|
||||
QtCore.qCritical("critical")
|
||||
|
||||
assert len(caplog.records) == 3
|
||||
caplog.records[0].message == "debug"
|
||||
caplog.records[0].levelno == logging.DEBUG
|
||||
caplog.records[1].message == "warning"
|
||||
caplog.records[1].levelno == logging.WARNING
|
||||
caplog.records[2].message == "critical"
|
||||
caplog.records[2].levelno == logging.CRITICAL
|
||||
0
tests/test_sliders/__init__.py
Normal file
0
tests/test_sliders/__init__.py
Normal file
70
tests/test_sliders/_testutil.py
Normal file
70
tests/test_sliders/_testutil.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from contextlib import suppress
|
||||
from distutils.version import LooseVersion
|
||||
from platform import system
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt.qtcompat import QT_VERSION
|
||||
from superqt.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt
|
||||
from superqt.qtcompat.QtGui import QMouseEvent, QWheelEvent
|
||||
|
||||
QT_VERSION = LooseVersion(QT_VERSION)
|
||||
|
||||
SYS_DARWIN = system() == "Darwin"
|
||||
|
||||
skip_on_linux_qt6 = pytest.mark.skipif(
|
||||
system() == "Linux" and QT_VERSION >= LooseVersion("6.0"),
|
||||
reason="hover events not working on linux pyqt6",
|
||||
)
|
||||
|
||||
|
||||
def _mouse_event(pos=QPointF(), type_=QEvent.MouseMove):
|
||||
"""Create a mouse event of `type_` at `pos`."""
|
||||
return QMouseEvent(type_, QPointF(pos), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
|
||||
|
||||
|
||||
def _wheel_event(arc):
|
||||
"""Create a wheel event with `arc`."""
|
||||
with suppress(TypeError):
|
||||
return QWheelEvent(
|
||||
QPointF(),
|
||||
QPointF(),
|
||||
QPoint(arc, arc),
|
||||
QPoint(arc, arc),
|
||||
Qt.NoButton,
|
||||
Qt.NoModifier,
|
||||
Qt.ScrollBegin,
|
||||
False,
|
||||
Qt.MouseEventSynthesizedByQt,
|
||||
)
|
||||
with suppress(TypeError):
|
||||
return QWheelEvent(
|
||||
QPointF(),
|
||||
QPointF(),
|
||||
QPoint(-arc, -arc),
|
||||
QPoint(-arc, -arc),
|
||||
1,
|
||||
Qt.Vertical,
|
||||
Qt.NoButton,
|
||||
Qt.NoModifier,
|
||||
Qt.ScrollBegin,
|
||||
False,
|
||||
Qt.MouseEventSynthesizedByQt,
|
||||
)
|
||||
|
||||
return QWheelEvent(
|
||||
QPointF(),
|
||||
QPointF(),
|
||||
QPoint(arc, arc),
|
||||
QPoint(arc, arc),
|
||||
1,
|
||||
Qt.Vertical,
|
||||
Qt.NoButton,
|
||||
Qt.NoModifier,
|
||||
)
|
||||
|
||||
|
||||
def _linspace(start, stop, n):
|
||||
h = (stop - start) / (n - 1)
|
||||
for i in range(n):
|
||||
yield start + h * i
|
||||
124
tests/test_sliders/test_float.py
Normal file
124
tests/test_sliders/test_float.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt import (
|
||||
QDoubleRangeSlider,
|
||||
QDoubleSlider,
|
||||
QLabeledDoubleRangeSlider,
|
||||
QLabeledDoubleSlider,
|
||||
)
|
||||
from superqt.qtcompat import API_NAME
|
||||
|
||||
range_types = {QDoubleRangeSlider, QLabeledDoubleRangeSlider}
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
QDoubleSlider,
|
||||
QLabeledDoubleSlider,
|
||||
QDoubleRangeSlider,
|
||||
QLabeledDoubleRangeSlider,
|
||||
]
|
||||
)
|
||||
def ds(qtbot, request):
|
||||
# convenience fixture that converts value() and setValue()
|
||||
# to let us use setValue((a, b)) for both range and non-range sliders
|
||||
cls = request.param
|
||||
wdg = cls()
|
||||
qtbot.addWidget(wdg)
|
||||
|
||||
def assert_val_type():
|
||||
type_ = float
|
||||
if cls in range_types:
|
||||
assert all([isinstance(i, type_) for i in wdg.value()]) # sourcery skip
|
||||
else:
|
||||
assert isinstance(wdg.value(), type_)
|
||||
|
||||
def assert_val_eq(val):
|
||||
assert wdg.value() == val if cls is QDoubleRangeSlider else val[0]
|
||||
|
||||
wdg.assert_val_type = assert_val_type
|
||||
wdg.assert_val_eq = assert_val_eq
|
||||
|
||||
if cls not in range_types:
|
||||
superset = wdg.setValue
|
||||
|
||||
def _safe_set(val):
|
||||
superset(val[0] if isinstance(val, tuple) else val)
|
||||
|
||||
wdg.setValue = _safe_set
|
||||
|
||||
return wdg
|
||||
|
||||
|
||||
def test_double_sliders(ds):
|
||||
ds.setMinimum(10)
|
||||
ds.setMaximum(99)
|
||||
ds.setValue((20, 40))
|
||||
ds.setSingleStep(1)
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
ds.assert_val_eq((20, 40))
|
||||
assert ds.singleStep() == 1
|
||||
|
||||
ds.assert_val_eq((20, 40))
|
||||
ds.assert_val_type()
|
||||
|
||||
ds.setValue((20.23, 40.23))
|
||||
ds.assert_val_eq((20.23, 40.23))
|
||||
ds.assert_val_type()
|
||||
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
assert ds.singleStep() == 1
|
||||
ds.assert_val_eq((20.23, 40.23))
|
||||
ds.setValue((20.2343, 40.2342))
|
||||
ds.assert_val_eq((20.2343, 40.2342))
|
||||
|
||||
ds.assert_val_eq((20.2343, 40.2342))
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
assert ds.singleStep() == 1
|
||||
|
||||
ds.assert_val_eq((20.2343, 40.2342))
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
assert ds.singleStep() == 1
|
||||
|
||||
|
||||
def test_double_sliders_small(ds):
|
||||
ds.setMaximum(1)
|
||||
ds.setValue((0.5, 0.9))
|
||||
assert ds.minimum() == 0
|
||||
assert ds.maximum() == 1
|
||||
ds.assert_val_eq((0.5, 0.9))
|
||||
|
||||
ds.setValue((0.122233, 0.72644353))
|
||||
ds.assert_val_eq((0.122233, 0.72644353))
|
||||
|
||||
|
||||
def test_double_sliders_big(ds):
|
||||
ds.setValue((20, 80))
|
||||
ds.setMaximum(5e14)
|
||||
assert ds.minimum() == 0
|
||||
assert ds.maximum() == 5e14
|
||||
ds.setValue((1.74e9, 1.432e10))
|
||||
ds.assert_val_eq((1.74e9, 1.432e10))
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
os.name == "nt" and API_NAME == "PyQt6", reason="Not ready for pyqt6"
|
||||
)
|
||||
def test_signals(ds, qtbot):
|
||||
with qtbot.waitSignal(ds.valueChanged):
|
||||
ds.setValue((10, 20))
|
||||
|
||||
with qtbot.waitSignal(ds.rangeChanged):
|
||||
ds.setMinimum(0.5)
|
||||
|
||||
with qtbot.waitSignal(ds.rangeChanged):
|
||||
ds.setMaximum(3.7)
|
||||
|
||||
with qtbot.waitSignal(ds.rangeChanged):
|
||||
ds.setRange(1.2, 3.3)
|
||||
184
tests/test_sliders/test_generic_slider.py
Normal file
184
tests/test_sliders/test_generic_slider.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import math
|
||||
import platform
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt
|
||||
from superqt.qtcompat.QtGui import QHoverEvent
|
||||
from superqt.qtcompat.QtWidgets import QStyle, QStyleOptionSlider
|
||||
from superqt.sliders._generic_slider import _GenericSlider, _sliderValueFromPosition
|
||||
|
||||
from ._testutil import _linspace, _mouse_event, _wheel_event, skip_on_linux_qt6
|
||||
|
||||
|
||||
@pytest.fixture(params=[Qt.Horizontal, Qt.Vertical])
|
||||
def gslider(qtbot, request):
|
||||
slider = _GenericSlider(request.param)
|
||||
qtbot.addWidget(slider)
|
||||
assert slider.value() == 0
|
||||
assert slider.minimum() == 0
|
||||
assert slider.maximum() == 99
|
||||
yield slider
|
||||
slider.initStyleOption(QStyleOptionSlider())
|
||||
|
||||
|
||||
def test_change_floatslider_range(gslider: _GenericSlider, qtbot):
|
||||
with qtbot.waitSignals([gslider.rangeChanged, gslider.valueChanged]):
|
||||
gslider.setMinimum(10)
|
||||
|
||||
assert gslider.value() == 10 == gslider.minimum()
|
||||
assert gslider.maximum() == 99
|
||||
|
||||
with qtbot.waitSignal(gslider.rangeChanged):
|
||||
gslider.setMaximum(90)
|
||||
assert gslider.value() == 10 == gslider.minimum()
|
||||
assert gslider.maximum() == 90
|
||||
|
||||
with qtbot.waitSignals([gslider.rangeChanged, gslider.valueChanged]):
|
||||
gslider.setRange(20, 40)
|
||||
assert gslider.value() == 20 == gslider.minimum()
|
||||
assert gslider.maximum() == 40
|
||||
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.setValue(30)
|
||||
assert gslider.value() == 30
|
||||
|
||||
with qtbot.waitSignals([gslider.rangeChanged, gslider.valueChanged]):
|
||||
gslider.setMaximum(25)
|
||||
assert gslider.value() == 25 == gslider.maximum()
|
||||
assert gslider.minimum() == 20
|
||||
|
||||
|
||||
def test_float_values(gslider: _GenericSlider, qtbot):
|
||||
with qtbot.waitSignal(gslider.rangeChanged):
|
||||
gslider.setRange(0.25, 0.75)
|
||||
assert gslider.minimum() == 0.25
|
||||
assert gslider.maximum() == 0.75
|
||||
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.setValue(0.55)
|
||||
assert gslider.value() == 0.55
|
||||
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.setValue(1.55)
|
||||
assert gslider.value() == 0.75 == gslider.maximum()
|
||||
|
||||
|
||||
def test_ticks(gslider: _GenericSlider, qtbot):
|
||||
gslider.setTickInterval(0.3)
|
||||
assert gslider.tickInterval() == 0.3
|
||||
gslider.setTickPosition(gslider.TicksAbove)
|
||||
gslider.show()
|
||||
|
||||
|
||||
def test_show(gslider, qtbot):
|
||||
gslider.show()
|
||||
|
||||
|
||||
@pytest.mark.skipif(platform.system() != "Darwin", reason="cross-platform is tricky")
|
||||
def test_press_move_release(gslider: _GenericSlider, qtbot):
|
||||
assert gslider._pressedControl == QStyle.SubControl.SC_None
|
||||
|
||||
opt = QStyleOptionSlider()
|
||||
gslider.initStyleOption(opt)
|
||||
style = gslider.style()
|
||||
hrect = style.subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle)
|
||||
handle_pos = gslider.mapToGlobal(hrect.center())
|
||||
|
||||
with qtbot.waitSignal(gslider.sliderPressed):
|
||||
qtbot.mousePress(gslider, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
assert gslider._pressedControl == QStyle.SubControl.SC_SliderHandle
|
||||
|
||||
with qtbot.waitSignals([gslider.sliderMoved, gslider.valueChanged]):
|
||||
shift = QPoint(0, -8) if gslider.orientation() == Qt.Vertical else QPoint(8, 0)
|
||||
gslider.mouseMoveEvent(_mouse_event(handle_pos + shift))
|
||||
|
||||
with qtbot.waitSignal(gslider.sliderReleased):
|
||||
qtbot.mouseRelease(gslider, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
assert gslider._pressedControl == QStyle.SubControl.SC_None
|
||||
|
||||
gslider.show()
|
||||
with qtbot.waitSignal(gslider.sliderPressed):
|
||||
qtbot.mousePress(gslider, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
|
||||
@skip_on_linux_qt6
|
||||
def test_hover(gslider: _GenericSlider):
|
||||
|
||||
opt = QStyleOptionSlider()
|
||||
gslider.initStyleOption(opt)
|
||||
style = gslider.style()
|
||||
hrect = style.subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle)
|
||||
handle_pos = QPointF(gslider.mapToGlobal(hrect.center()))
|
||||
|
||||
assert gslider._hoverControl == QStyle.SubControl.SC_None
|
||||
|
||||
gslider.event(QHoverEvent(QEvent.HoverEnter, handle_pos, QPointF()))
|
||||
assert gslider._hoverControl == QStyle.SubControl.SC_SliderHandle
|
||||
|
||||
gslider.event(QHoverEvent(QEvent.HoverLeave, QPointF(-1000, -1000), handle_pos))
|
||||
assert gslider._hoverControl == QStyle.SubControl.SC_None
|
||||
|
||||
|
||||
def test_wheel(gslider: _GenericSlider, qtbot):
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.wheelEvent(_wheel_event(120))
|
||||
|
||||
gslider.wheelEvent(_wheel_event(0))
|
||||
|
||||
|
||||
def test_position(gslider: _GenericSlider, qtbot):
|
||||
gslider.setSliderPosition(21.2)
|
||||
assert gslider.sliderPosition() == 21.2
|
||||
|
||||
|
||||
def test_steps(gslider: _GenericSlider, qtbot):
|
||||
gslider.setSingleStep(0.1)
|
||||
assert gslider.singleStep() == 0.1
|
||||
|
||||
gslider.setSingleStep(1.5e20)
|
||||
assert gslider.singleStep() == 1.5e20
|
||||
|
||||
gslider.setPageStep(0.2)
|
||||
assert gslider.pageStep() == 0.2
|
||||
|
||||
gslider.setPageStep(1.5e30)
|
||||
assert gslider.pageStep() == 1.5e30
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mag", list(range(4, 37, 4)) + list(range(-4, -37, -4)))
|
||||
def test_slider_extremes(gslider: _GenericSlider, mag, qtbot):
|
||||
_mag = 10 ** mag
|
||||
with qtbot.waitSignal(gslider.rangeChanged):
|
||||
gslider.setRange(-_mag, _mag)
|
||||
for i in _linspace(-_mag, _mag, 10):
|
||||
gslider.setValue(i)
|
||||
assert math.isclose(gslider.value(), i, rel_tol=1e-8)
|
||||
gslider.initStyleOption(QStyleOptionSlider())
|
||||
|
||||
|
||||
# args are (min: float, max: float, position: int, span: int, upsideDown: bool)
|
||||
@pytest.mark.parametrize(
|
||||
"args, result",
|
||||
[
|
||||
# (min, max, pos, span[, inverted]), expectation
|
||||
# data range (1, 2)
|
||||
((1, 2, 50, 100), 1.5),
|
||||
((1, 2, 70, 100), 1.7),
|
||||
((1, 2, 70, 100, True), 1.3), # inverted appearance
|
||||
((1, 2, 170, 100), 2),
|
||||
((1, 2, 100, 100), 2),
|
||||
((1, 2, -30, 100), 1),
|
||||
# data range (-2, 2)
|
||||
((-2, 2, 50, 100), 0),
|
||||
((-2, 2, 75, 100), 1),
|
||||
((-2, 2, 75, 100, True), -1), # inverted appearance
|
||||
((-2, 2, 170, 100), 2),
|
||||
((-2, 2, 100, 100), 2),
|
||||
((-2, 2, -30, 100), -2),
|
||||
],
|
||||
)
|
||||
def test_slider_value_from_position(args, result):
|
||||
assert math.isclose(_sliderValueFromPosition(*args), result)
|
||||
11
tests/test_sliders/test_labeled_slider.py
Normal file
11
tests/test_sliders/test_labeled_slider.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from superqt import QLabeledRangeSlider
|
||||
|
||||
|
||||
def test_labeled_slider_api(qtbot):
|
||||
slider = QLabeledRangeSlider()
|
||||
qtbot.addWidget(slider)
|
||||
slider.hideBar()
|
||||
slider.showBar()
|
||||
slider.setBarVisible()
|
||||
slider.setBarMovesAllHandles()
|
||||
slider.setBarIsRigid()
|
||||
156
tests/test_sliders/test_range_slider.py
Normal file
156
tests/test_sliders/test_range_slider.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt import QDoubleRangeSlider, QRangeSlider
|
||||
from superqt.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt
|
||||
from superqt.qtcompat.QtGui import QHoverEvent
|
||||
from superqt.qtcompat.QtWidgets import QStyle, QStyleOptionSlider
|
||||
|
||||
from ._testutil import _linspace, _mouse_event, _wheel_event, skip_on_linux_qt6
|
||||
|
||||
|
||||
@pytest.fixture(params=[Qt.Horizontal, Qt.Vertical])
|
||||
def gslider(qtbot, request):
|
||||
slider = QDoubleRangeSlider(request.param)
|
||||
qtbot.addWidget(slider)
|
||||
assert slider.value() == (20, 80)
|
||||
assert slider.minimum() == 0
|
||||
assert slider.maximum() == 99
|
||||
yield slider
|
||||
slider.initStyleOption(QStyleOptionSlider())
|
||||
|
||||
|
||||
def test_change_floatslider_range(gslider: QRangeSlider, qtbot):
|
||||
with qtbot.waitSignals([gslider.rangeChanged, gslider.valueChanged]):
|
||||
gslider.setMinimum(30)
|
||||
|
||||
assert gslider.value()[0] == 30 == gslider.minimum()
|
||||
assert gslider.maximum() == 99
|
||||
|
||||
with qtbot.waitSignal(gslider.rangeChanged):
|
||||
gslider.setMaximum(70)
|
||||
assert gslider.value()[0] == 30 == gslider.minimum()
|
||||
assert gslider.value()[1] == 70 == gslider.maximum()
|
||||
|
||||
with qtbot.waitSignals([gslider.rangeChanged, gslider.valueChanged]):
|
||||
gslider.setRange(40, 60)
|
||||
assert gslider.value()[0] == 40 == gslider.minimum()
|
||||
assert gslider.maximum() == 60
|
||||
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.setValue([40, 50])
|
||||
assert gslider.value()[0] == 40 == gslider.minimum()
|
||||
assert gslider.value()[1] == 50
|
||||
|
||||
with qtbot.waitSignals([gslider.rangeChanged, gslider.valueChanged]):
|
||||
gslider.setMaximum(45)
|
||||
assert gslider.value()[0] == 40 == gslider.minimum()
|
||||
assert gslider.value()[1] == 45 == gslider.maximum()
|
||||
|
||||
|
||||
def test_float_values(gslider: QRangeSlider, qtbot):
|
||||
with qtbot.waitSignal(gslider.rangeChanged):
|
||||
gslider.setRange(0.1, 0.9)
|
||||
assert gslider.minimum() == 0.1
|
||||
assert gslider.maximum() == 0.9
|
||||
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.setValue([0.4, 0.6])
|
||||
assert gslider.value() == (0.4, 0.6)
|
||||
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.setValue([0, 1.9])
|
||||
assert gslider.value()[0] == 0.1 == gslider.minimum()
|
||||
assert gslider.value()[1] == 0.9 == gslider.maximum()
|
||||
|
||||
|
||||
def test_position(gslider: QRangeSlider, qtbot):
|
||||
gslider.setSliderPosition([10, 80])
|
||||
assert gslider.sliderPosition() == (10, 80)
|
||||
|
||||
|
||||
def test_steps(gslider: QRangeSlider, qtbot):
|
||||
gslider.setSingleStep(0.1)
|
||||
assert gslider.singleStep() == 0.1
|
||||
|
||||
gslider.setSingleStep(1.5e20)
|
||||
assert gslider.singleStep() == 1.5e20
|
||||
|
||||
gslider.setPageStep(0.2)
|
||||
assert gslider.pageStep() == 0.2
|
||||
|
||||
gslider.setPageStep(1.5e30)
|
||||
assert gslider.pageStep() == 1.5e30
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mag", list(range(4, 37, 4)) + list(range(-4, -37, -4)))
|
||||
def test_slider_extremes(gslider: QRangeSlider, mag, qtbot):
|
||||
_mag = 10 ** mag
|
||||
with qtbot.waitSignal(gslider.rangeChanged):
|
||||
gslider.setRange(-_mag, _mag)
|
||||
for i in _linspace(-_mag, _mag, 10):
|
||||
gslider.setValue((i, _mag))
|
||||
assert math.isclose(gslider.value()[0], i, rel_tol=1e-8)
|
||||
gslider.initStyleOption(QStyleOptionSlider())
|
||||
|
||||
|
||||
def test_ticks(gslider: QRangeSlider, qtbot):
|
||||
gslider.setTickInterval(0.3)
|
||||
assert gslider.tickInterval() == 0.3
|
||||
gslider.setTickPosition(gslider.TicksAbove)
|
||||
gslider.show()
|
||||
|
||||
|
||||
def test_show(gslider, qtbot):
|
||||
gslider.show()
|
||||
|
||||
|
||||
def test_press_move_release(gslider: QRangeSlider, qtbot):
|
||||
assert gslider._pressedControl == QStyle.SubControl.SC_None
|
||||
|
||||
opt = QStyleOptionSlider()
|
||||
gslider.initStyleOption(opt)
|
||||
style = gslider.style()
|
||||
hrect = style.subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle)
|
||||
handle_pos = gslider.mapToGlobal(hrect.center())
|
||||
|
||||
with qtbot.waitSignal(gslider.sliderPressed):
|
||||
qtbot.mousePress(gslider, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
assert gslider._pressedControl == QStyle.SubControl.SC_SliderHandle
|
||||
|
||||
with qtbot.waitSignals([gslider.sliderMoved, gslider.valueChanged]):
|
||||
shift = QPoint(0, -8) if gslider.orientation() == Qt.Vertical else QPoint(8, 0)
|
||||
gslider.mouseMoveEvent(_mouse_event(handle_pos + shift))
|
||||
|
||||
with qtbot.waitSignal(gslider.sliderReleased):
|
||||
qtbot.mouseRelease(gslider, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
assert gslider._pressedControl == QStyle.SubControl.SC_None
|
||||
|
||||
gslider.show()
|
||||
with qtbot.waitSignal(gslider.sliderPressed):
|
||||
qtbot.mousePress(gslider, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
|
||||
@skip_on_linux_qt6
|
||||
def test_hover(gslider: QRangeSlider):
|
||||
|
||||
hrect = gslider._handleRect(0)
|
||||
handle_pos = QPointF(gslider.mapToGlobal(hrect.center()))
|
||||
|
||||
assert gslider._hoverControl == QStyle.SubControl.SC_None
|
||||
|
||||
gslider.event(QHoverEvent(QEvent.HoverEnter, handle_pos, QPointF()))
|
||||
assert gslider._hoverControl == QStyle.SubControl.SC_SliderHandle
|
||||
|
||||
gslider.event(QHoverEvent(QEvent.HoverLeave, QPointF(-1000, -1000), handle_pos))
|
||||
assert gslider._hoverControl == QStyle.SubControl.SC_None
|
||||
|
||||
|
||||
def test_wheel(gslider: QRangeSlider, qtbot):
|
||||
with qtbot.waitSignal(gslider.valueChanged):
|
||||
gslider.wheelEvent(_wheel_event(120))
|
||||
|
||||
gslider.wheelEvent(_wheel_event(0))
|
||||
223
tests/test_sliders/test_single_value_sliders.py
Normal file
223
tests/test_sliders/test_single_value_sliders.py
Normal file
@@ -0,0 +1,223 @@
|
||||
import math
|
||||
import platform
|
||||
from contextlib import suppress
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt import QDoubleSlider, QLabeledDoubleSlider, QLabeledSlider
|
||||
from superqt.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt
|
||||
from superqt.qtcompat.QtGui import QHoverEvent
|
||||
from superqt.qtcompat.QtWidgets import QSlider, QStyle, QStyleOptionSlider
|
||||
from superqt.sliders._generic_slider import _GenericSlider
|
||||
|
||||
from ._testutil import (
|
||||
QT_VERSION,
|
||||
_linspace,
|
||||
_mouse_event,
|
||||
_wheel_event,
|
||||
skip_on_linux_qt6,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(params=[Qt.Horizontal, Qt.Vertical], ids=["horizontal", "vertical"])
|
||||
def orientation(request):
|
||||
return request.param
|
||||
|
||||
|
||||
START_MI_MAX_VAL = (0, 99, 0)
|
||||
TEST_SLIDERS = [QDoubleSlider, QLabeledSlider, QLabeledDoubleSlider]
|
||||
|
||||
|
||||
def _assert_value_in_range(sld):
|
||||
val = sld.value()
|
||||
if isinstance(val, (int, float)):
|
||||
val = (val,)
|
||||
assert all(sld.minimum() <= v <= sld.maximum() for v in val)
|
||||
|
||||
|
||||
@pytest.fixture(params=TEST_SLIDERS)
|
||||
def sld(request, qtbot, orientation):
|
||||
Cls = request.param
|
||||
slider = Cls(orientation)
|
||||
slider.setRange(*START_MI_MAX_VAL[:2])
|
||||
slider.setValue(START_MI_MAX_VAL[2])
|
||||
qtbot.addWidget(slider)
|
||||
assert (slider.minimum(), slider.maximum(), slider.value()) == START_MI_MAX_VAL
|
||||
_assert_value_in_range(slider)
|
||||
yield slider
|
||||
_assert_value_in_range(slider)
|
||||
with suppress(AttributeError):
|
||||
slider.initStyleOption(QStyleOptionSlider())
|
||||
|
||||
|
||||
def called_with(*expected_result):
|
||||
"""Use in check_params_cbs to assert that a callback is called as expected.
|
||||
|
||||
e.g. `called_with(20, 50)` returns a callback that checks that the callback
|
||||
is called with the arguments (20, 50)
|
||||
"""
|
||||
|
||||
def check_emitted_values(*values):
|
||||
return values == expected_result
|
||||
|
||||
return check_emitted_values
|
||||
|
||||
|
||||
def test_change_floatslider_range(sld: _GenericSlider, qtbot):
|
||||
BOTH = [sld.rangeChanged, sld.valueChanged]
|
||||
|
||||
for signals, checks, funcname, args in [
|
||||
(BOTH, [called_with(10, 99), called_with(10)], "setMinimum", (10,)),
|
||||
([sld.rangeChanged], [called_with(10, 90)], "setMaximum", (90,)),
|
||||
(BOTH, [called_with(20, 40), called_with(20)], "setRange", (20, 40)),
|
||||
([sld.valueChanged], [called_with(30)], "setValue", (30,)),
|
||||
(BOTH, [called_with(20, 25), called_with(25)], "setMaximum", (25,)),
|
||||
([sld.valueChanged], [called_with(23)], "setValue", (23,)),
|
||||
]:
|
||||
with qtbot.waitSignals(signals, check_params_cbs=checks, timeout=500):
|
||||
getattr(sld, funcname)(*args)
|
||||
_assert_value_in_range(sld)
|
||||
|
||||
|
||||
def test_float_values(sld: _GenericSlider, qtbot):
|
||||
if type(sld) is QLabeledSlider:
|
||||
pytest.skip()
|
||||
for signals, checks, funcname, args in [
|
||||
(sld.rangeChanged, called_with(0.1, 0.9), "setRange", (0.1, 0.9)),
|
||||
(sld.valueChanged, called_with(0.4), "setValue", (0.4,)),
|
||||
(sld.valueChanged, called_with(0.1), "setValue", (0,)),
|
||||
(sld.valueChanged, called_with(0.9), "setValue", (1.9,)),
|
||||
]:
|
||||
with qtbot.waitSignal(signals, check_params_cb=checks, timeout=400):
|
||||
getattr(sld, funcname)(*args)
|
||||
_assert_value_in_range(sld)
|
||||
|
||||
|
||||
def test_ticks(sld: _GenericSlider, qtbot):
|
||||
sld.setTickInterval(3)
|
||||
assert sld.tickInterval() == 3
|
||||
sld.setTickPosition(QSlider.TicksAbove)
|
||||
sld.show()
|
||||
|
||||
|
||||
@pytest.mark.skipif(platform.system() != "Darwin", reason="cross-platform is tricky")
|
||||
def test_press_move_release(sld: _GenericSlider, qtbot):
|
||||
if hasattr(sld, "_slider") and sld._slider.orientation() == Qt.Vertical:
|
||||
pytest.xfail("test failing for vertical at the moment")
|
||||
|
||||
_real_sld = getattr(sld, "_slider", sld)
|
||||
|
||||
with suppress(AttributeError): # for QSlider
|
||||
assert _real_sld._pressedControl == QStyle.SubControl.SC_None
|
||||
|
||||
opt = QStyleOptionSlider()
|
||||
_real_sld.initStyleOption(opt)
|
||||
style = _real_sld.style()
|
||||
hrect = style.subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle)
|
||||
handle_pos = _real_sld.mapToGlobal(hrect.center())
|
||||
|
||||
with qtbot.waitSignal(_real_sld.sliderPressed, timeout=300):
|
||||
qtbot.mousePress(_real_sld, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
with suppress(AttributeError):
|
||||
assert sld._pressedControl == QStyle.SubControl.SC_SliderHandle
|
||||
|
||||
with qtbot.waitSignals(
|
||||
[_real_sld.sliderMoved, _real_sld.valueChanged], timeout=300
|
||||
):
|
||||
shift = (
|
||||
QPoint(0, -8) if _real_sld.orientation() == Qt.Vertical else QPoint(8, 0)
|
||||
)
|
||||
_real_sld.mouseMoveEvent(_mouse_event(handle_pos + shift))
|
||||
|
||||
with qtbot.waitSignal(_real_sld.sliderReleased, timeout=300):
|
||||
qtbot.mouseRelease(_real_sld, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
with suppress(AttributeError):
|
||||
assert _real_sld._pressedControl == QStyle.SubControl.SC_None
|
||||
|
||||
sld.show()
|
||||
with qtbot.waitSignal(_real_sld.sliderPressed, timeout=300):
|
||||
qtbot.mousePress(_real_sld, Qt.LeftButton, pos=handle_pos)
|
||||
|
||||
|
||||
@skip_on_linux_qt6
|
||||
def test_hover(sld: _GenericSlider):
|
||||
|
||||
_real_sld = getattr(sld, "_slider", sld)
|
||||
|
||||
opt = QStyleOptionSlider()
|
||||
_real_sld.initStyleOption(opt)
|
||||
hrect = _real_sld.style().subControlRect(
|
||||
QStyle.CC_Slider, opt, QStyle.SC_SliderHandle
|
||||
)
|
||||
handle_pos = QPointF(sld.mapToGlobal(hrect.center()))
|
||||
|
||||
with suppress(AttributeError): # for QSlider
|
||||
assert _real_sld._hoverControl == QStyle.SubControl.SC_None
|
||||
|
||||
_real_sld.event(QHoverEvent(QEvent.HoverEnter, handle_pos, QPointF()))
|
||||
with suppress(AttributeError): # for QSlider
|
||||
assert _real_sld._hoverControl == QStyle.SubControl.SC_SliderHandle
|
||||
|
||||
_real_sld.event(QHoverEvent(QEvent.HoverLeave, QPointF(-1000, -1000), handle_pos))
|
||||
with suppress(AttributeError): # for QSlider
|
||||
assert _real_sld._hoverControl == QStyle.SubControl.SC_None
|
||||
|
||||
|
||||
def test_wheel(sld: _GenericSlider, qtbot):
|
||||
|
||||
if type(sld) is QLabeledSlider and QT_VERSION < LooseVersion("5.12"):
|
||||
pytest.skip()
|
||||
|
||||
_real_sld = getattr(sld, "_slider", sld)
|
||||
with qtbot.waitSignal(sld.valueChanged, timeout=400):
|
||||
_real_sld.wheelEvent(_wheel_event(120))
|
||||
|
||||
_real_sld.wheelEvent(_wheel_event(0))
|
||||
|
||||
|
||||
def test_position(sld: _GenericSlider, qtbot):
|
||||
sld.setSliderPosition(21)
|
||||
assert sld.sliderPosition() == 21
|
||||
|
||||
if type(sld) is not QLabeledSlider:
|
||||
sld.setSliderPosition(21.5)
|
||||
assert sld.sliderPosition() == 21.5
|
||||
|
||||
|
||||
def test_steps(sld: _GenericSlider, qtbot):
|
||||
|
||||
sld.setSingleStep(11)
|
||||
assert sld.singleStep() == 11
|
||||
|
||||
sld.setPageStep(16)
|
||||
assert sld.pageStep() == 16
|
||||
|
||||
if type(sld) is not QLabeledSlider:
|
||||
|
||||
sld.setSingleStep(0.1)
|
||||
assert sld.singleStep() == 0.1
|
||||
|
||||
sld.setSingleStep(1.5e20)
|
||||
assert sld.singleStep() == 1.5e20
|
||||
|
||||
sld.setPageStep(0.2)
|
||||
assert sld.pageStep() == 0.2
|
||||
|
||||
sld.setPageStep(1.5e30)
|
||||
assert sld.pageStep() == 1.5e30
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mag", list(range(4, 37, 4)) + list(range(-4, -37, -4)))
|
||||
def test_slider_extremes(sld: _GenericSlider, mag, qtbot):
|
||||
if type(sld) is QLabeledSlider:
|
||||
pytest.skip()
|
||||
|
||||
_mag = 10 ** mag
|
||||
with qtbot.waitSignal(sld.rangeChanged, timeout=400):
|
||||
sld.setRange(-_mag, _mag)
|
||||
for i in _linspace(-_mag, _mag, 10):
|
||||
sld.setValue(i)
|
||||
assert math.isclose(sld.value(), i, rel_tol=1e-8)
|
||||
142
tests/test_sliders/test_slider.py
Normal file
142
tests/test_sliders/test_slider.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import platform
|
||||
|
||||
import pytest
|
||||
|
||||
from superqt import QRangeSlider
|
||||
from superqt.qtcompat import API_NAME
|
||||
from superqt.qtcompat.QtCore import Qt
|
||||
from superqt.sliders._generic_range_slider import SC_BAR, SC_HANDLE, SC_NONE
|
||||
|
||||
NOT_LINUX = platform.system() != "Linux"
|
||||
NOT_PYSIDE2 = API_NAME != "PySide2"
|
||||
|
||||
skipmouse = pytest.mark.skipif(NOT_LINUX or NOT_PYSIDE2, reason="mouse tests finicky")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("orientation", ["Horizontal", "Vertical"])
|
||||
def test_basic(qtbot, orientation):
|
||||
rs = QRangeSlider(getattr(Qt, orientation))
|
||||
qtbot.addWidget(rs)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("orientation", ["Horizontal", "Vertical"])
|
||||
def test_value(qtbot, orientation):
|
||||
rs = QRangeSlider(getattr(Qt, orientation))
|
||||
qtbot.addWidget(rs)
|
||||
rs.setValue([10, 20])
|
||||
assert rs.value() == (10, 20)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("orientation", ["Horizontal", "Vertical"])
|
||||
def test_range(qtbot, orientation):
|
||||
rs = QRangeSlider(getattr(Qt, orientation))
|
||||
qtbot.addWidget(rs)
|
||||
rs.setValue([10, 20])
|
||||
assert rs.value() == (10, 20)
|
||||
rs.setRange(15, 20)
|
||||
assert rs.value() == (15, 20)
|
||||
assert rs.minimum() == 15
|
||||
assert rs.maximum() == 20
|
||||
|
||||
|
||||
@skipmouse
|
||||
def test_drag_handles(qtbot):
|
||||
rs = QRangeSlider(Qt.Horizontal)
|
||||
qtbot.addWidget(rs)
|
||||
rs.setRange(0, 99)
|
||||
rs.setValue((20, 80))
|
||||
rs.setMouseTracking(True)
|
||||
rs.show()
|
||||
|
||||
# press the left handle
|
||||
pos = rs._handleRect(0).center()
|
||||
with qtbot.waitSignal(rs.sliderPressed):
|
||||
qtbot.mousePress(rs, Qt.LeftButton, pos=pos)
|
||||
assert rs._pressedControl == SC_HANDLE
|
||||
assert rs._pressedIndex == 0
|
||||
|
||||
# drag the left handle
|
||||
with qtbot.waitSignals([rs.sliderMoved] * 13): # couple less signals
|
||||
for _ in range(15):
|
||||
pos.setX(pos.x() + 2)
|
||||
qtbot.mouseMove(rs, pos)
|
||||
|
||||
with qtbot.waitSignal(rs.sliderReleased):
|
||||
qtbot.mouseRelease(rs, Qt.LeftButton)
|
||||
|
||||
# check the values
|
||||
assert rs.value()[0] > 30
|
||||
assert rs._pressedControl == SC_NONE
|
||||
|
||||
# press the right handle
|
||||
pos = rs._handleRect(1).center()
|
||||
with qtbot.waitSignal(rs.sliderPressed):
|
||||
qtbot.mousePress(rs, Qt.LeftButton, pos=pos)
|
||||
assert rs._pressedControl == SC_HANDLE
|
||||
assert rs._pressedIndex == 1
|
||||
|
||||
# drag the right handle
|
||||
with qtbot.waitSignals([rs.sliderMoved] * 13): # couple less signals
|
||||
for _ in range(15):
|
||||
pos.setX(pos.x() - 2)
|
||||
qtbot.mouseMove(rs, pos)
|
||||
with qtbot.waitSignal(rs.sliderReleased):
|
||||
qtbot.mouseRelease(rs, Qt.LeftButton)
|
||||
|
||||
# check the values
|
||||
assert rs.value()[1] < 70
|
||||
assert rs._pressedControl == SC_NONE
|
||||
|
||||
|
||||
@skipmouse
|
||||
def test_drag_handles_beyond_edge(qtbot):
|
||||
rs = QRangeSlider(Qt.Horizontal)
|
||||
qtbot.addWidget(rs)
|
||||
rs.setRange(0, 99)
|
||||
rs.setValue((20, 80))
|
||||
rs.setMouseTracking(True)
|
||||
rs.show()
|
||||
|
||||
# press the right handle
|
||||
pos = rs._handleRect(1).center()
|
||||
with qtbot.waitSignal(rs.sliderPressed):
|
||||
qtbot.mousePress(rs, Qt.LeftButton, pos=pos)
|
||||
assert rs._pressedControl == SC_HANDLE
|
||||
assert rs._pressedIndex == 1
|
||||
|
||||
# drag the handle off the right edge and make sure the value gets to the max
|
||||
for _ in range(7):
|
||||
pos.setX(pos.x() + 10)
|
||||
qtbot.mouseMove(rs, pos)
|
||||
|
||||
with qtbot.waitSignal(rs.sliderReleased):
|
||||
qtbot.mouseRelease(rs, Qt.LeftButton)
|
||||
|
||||
assert rs.value()[1] == 99
|
||||
|
||||
|
||||
@skipmouse
|
||||
def test_bar_drag_beyond_edge(qtbot):
|
||||
rs = QRangeSlider(Qt.Horizontal)
|
||||
qtbot.addWidget(rs)
|
||||
rs.setRange(0, 99)
|
||||
rs.setValue((20, 80))
|
||||
rs.setMouseTracking(True)
|
||||
rs.show()
|
||||
|
||||
# press the right handle
|
||||
pos = rs.rect().center()
|
||||
with qtbot.waitSignal(rs.sliderPressed):
|
||||
qtbot.mousePress(rs, Qt.LeftButton, pos=pos)
|
||||
assert rs._pressedControl == SC_BAR
|
||||
assert rs._pressedIndex == 1
|
||||
|
||||
# drag the handle off the right edge and make sure the value gets to the max
|
||||
for _ in range(15):
|
||||
pos.setX(pos.x() + 10)
|
||||
qtbot.mouseMove(rs, pos)
|
||||
|
||||
with qtbot.waitSignal(rs.sliderReleased):
|
||||
qtbot.mouseRelease(rs, Qt.LeftButton)
|
||||
|
||||
assert rs.value()[1] == 99
|
||||
Reference in New Issue
Block a user