mirror of
https://github.com/pyapp-kit/superqt.git
synced 2025-12-13 09:50:05 +01:00
261 lines
8.4 KiB
Python
261 lines
8.4 KiB
Python
import math
|
|
import platform
|
|
from contextlib import suppress
|
|
|
|
import pytest
|
|
from qtpy.QtCore import QEvent, QPoint, QPointF, Qt
|
|
from qtpy.QtWidgets import QSlider, QStyle, QStyleOptionSlider
|
|
|
|
from superqt import QDoubleSlider, QLabeledDoubleSlider, QLabeledSlider
|
|
from superqt.sliders._generic_slider import _GenericSlider
|
|
|
|
from ._testutil import (
|
|
QT_VERSION,
|
|
_hover_event,
|
|
_linspace,
|
|
_mouse_event,
|
|
_wheel_event,
|
|
skip_on_linux_qt6,
|
|
)
|
|
|
|
|
|
@pytest.fixture(
|
|
params=[Qt.Orientation.Horizontal, Qt.Orientation.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.TickPosition.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.Orientation.Vertical:
|
|
pytest.xfail("test failing for vertical at the moment")
|
|
# this fail on vertical came with pyside6.2 ... need to debug
|
|
# still works in practice, but test fails to catch signals
|
|
if sld.orientation() == Qt.Orientation.Vertical:
|
|
pytest.xfail()
|
|
|
|
_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.ComplexControl.CC_Slider, opt, QStyle.SubControl.SC_SliderHandle
|
|
)
|
|
handle_pos = _real_sld.mapToGlobal(hrect.center())
|
|
|
|
with qtbot.waitSignal(_real_sld.sliderPressed, timeout=300):
|
|
qtbot.mousePress(_real_sld, Qt.MouseButton.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.Orientation.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.MouseButton.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.MouseButton.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.ComplexControl.CC_Slider, opt, QStyle.SubControl.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(_hover_event(QEvent.Type.HoverEnter, handle_pos, QPointF(), sld))
|
|
with suppress(AttributeError): # for QSlider
|
|
assert _real_sld._hoverControl == QStyle.SubControl.SC_SliderHandle
|
|
|
|
_real_sld.event(
|
|
_hover_event(QEvent.Type.HoverLeave, QPointF(-1000, -1000), handle_pos, sld)
|
|
)
|
|
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 < (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)
|
|
|
|
|
|
def test_slider_with_equal_min_max(sld: _GenericSlider, qtbot):
|
|
"""Test that slider works when min == max (issue #307).
|
|
|
|
Previously, this would raise a TypeError: 'float' object cannot be
|
|
interpreted as an integer when calling show() because _to_qinteger_space
|
|
returned a float instead of an int when range was 0.
|
|
"""
|
|
# Test with min=max=99 (the specific case from issue #307)
|
|
with qtbot.waitSignal(sld.rangeChanged, timeout=400):
|
|
sld.setMinimum(99)
|
|
|
|
# This should not raise a TypeError
|
|
sld.show()
|
|
|
|
# Verify the slider state
|
|
assert sld.minimum() == 99
|
|
assert sld.maximum() == 99
|
|
assert sld.value() == 99
|
|
|
|
# Test that we can also set max first
|
|
with qtbot.waitSignal(sld.rangeChanged, timeout=400):
|
|
sld.setMaximum(0)
|
|
|
|
sld.show()
|
|
assert sld.minimum() == 0
|
|
assert sld.maximum() == 0
|
|
assert sld.value() == 0
|