File: test_qt_compat.py

package info (click to toggle)
psygnal 0.11.1-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 636 kB
  • sloc: python: 7,604; makefile: 8
file content (146 lines) | stat: -rw-r--r-- 4,210 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""qtbot should work for testing!"""

from threading import Thread, current_thread, main_thread
from typing import TYPE_CHECKING, Any, Callable, Literal, Tuple
from unittest.mock import Mock

import pytest

from psygnal import Signal
from psygnal._signal import _guess_qtsignal_signature

pytest.importorskip("pytestqt")
if TYPE_CHECKING:
    from pytestqt.qtbot import QtBot


def _equals(*val: Any) -> Callable[[Tuple[Any, ...]], bool]:
    def _inner(*other: Any) -> bool:
        return other == val

    return _inner


def test_wait_signals(qtbot: "QtBot") -> None:
    class Emitter:
        sig1 = Signal()
        sig2 = Signal(int)
        sig3 = Signal(int, int)

    e = Emitter()

    with qtbot.waitSignal(e.sig2, check_params_cb=_equals(1)):
        e.sig2.emit(1)

    with qtbot.waitSignal(e.sig3, check_params_cb=_equals(2, 3)):
        e.sig3.emit(2, 3)

    with qtbot.waitSignals([e.sig3], check_params_cbs=[_equals(2, 3)]):
        e.sig3.emit(2, 3)

    signals = [e.sig1, e.sig2, e.sig3, e.sig1]
    checks = [_equals(), _equals(1), _equals(2, 3), _equals()]
    with qtbot.waitSignals(signals, check_params_cbs=checks, order="strict"):
        e.sig1.emit()
        e.sig2.emit(1)
        e.sig3.emit(2, 3)
        e.sig1.emit()


def test_guess_signal_sig(qtbot: "QtBot") -> None:
    from qtpy import QtCore

    class QtObject(QtCore.QObject):
        qsig1 = QtCore.Signal()
        qsig2 = QtCore.Signal(int)
        qsig3 = QtCore.Signal(int, str)

    q_obj = QtObject()
    assert "qsig1()" in _guess_qtsignal_signature(q_obj.qsig1)
    assert "qsig1()" in _guess_qtsignal_signature(q_obj.qsig1.emit)
    assert "qsig2(int)" in _guess_qtsignal_signature(q_obj.qsig2)
    assert "qsig2(int)" in _guess_qtsignal_signature(q_obj.qsig2.emit)
    assert "qsig3(int,QString)" in _guess_qtsignal_signature(q_obj.qsig3)
    assert "qsig3(int,QString)" in _guess_qtsignal_signature(q_obj.qsig3.emit)


def test_connect_qt_signal_instance(qtbot: "QtBot") -> None:
    from qtpy import QtCore

    class Emitter:
        sig1 = Signal()
        sig2 = Signal(int)
        sig3 = Signal(int, int)

    class QtObject(QtCore.QObject):
        qsig1 = QtCore.Signal()
        qsig2 = QtCore.Signal(int)

    q_obj = QtObject()
    e = Emitter()

    # the hard case: signal.emit takes less args than we emit
    def test_receives_1(value: int) -> bool:
        # making sure that qsig2.emit only receives and emits 1 value
        return value == 1

    e.sig3.connect(q_obj.qsig2.emit)
    with qtbot.waitSignal(q_obj.qsig2, check_params_cb=test_receives_1):
        e.sig3.emit(1, 2)  # too many

    # the "standard" cases, where params match
    e.sig1.connect(q_obj.qsig1.emit)
    with qtbot.waitSignal(q_obj.qsig1):
        e.sig1.emit()

    e.sig2.connect(q_obj.qsig2.emit)
    with qtbot.waitSignal(q_obj.qsig2):
        e.sig2.emit(1)

    # the flip case: signal.emit takes more args than we emit
    with pytest.raises(ValueError):
        e.sig1.connect(q_obj.qsig2.emit)
    e.sig1.emit()


@pytest.mark.parametrize("thread", [None, "main"])
def test_q_main_thread_emit(
    thread: Literal["main", None], qtbot: "QtBot", qapp
) -> None:
    """Test using signal.emit(..., queue=True)

    ... and receiving it on the main thread with a QTimer connected to `emit_queued`
    """
    from psygnal.qt import start_emitting_from_queue, stop_emitting_from_queue

    class C:
        sig = Signal(int)

    obj = C()
    mock = Mock()

    @obj.sig.connect(thread=thread)
    def _some_slot(val: int) -> None:
        mock(val)
        assert (current_thread() == main_thread()) == (thread == "main")

    def _emit_from_thread() -> None:
        assert current_thread() != main_thread()
        obj.sig.emit(1)

    with qtbot.waitSignal(obj.sig, timeout=1000):
        t = Thread(target=_emit_from_thread)
        t.start()
        t.join()

    qapp.processEvents()
    if thread is None:
        mock.assert_called_once_with(1)
    else:
        mock.assert_not_called()
        start_emitting_from_queue()
        qapp.processEvents()
        mock.assert_called_once_with(1)

        start_emitting_from_queue(10)  # just for test coverage
        stop_emitting_from_queue()