Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4674.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- A videónéző-próba elkülöníti a QTest-kattintásokat, és minden gesztus állapotát naplózza (#4674).
221 changes: 123 additions & 98 deletions tests/app/qml_video_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,47 @@
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")


def varj_kattintasablak_vegere(
app,
utolso_kattintas: float,
intervallum_ms: int,
QTEST_EGER_DELAY_MS = 10


def qtest_egerlepes(
qtest,
window,
point,
button,
double_click_interval_ms: int,
*,
ora=None,
alvas=None,
tartalek_ms: int = 100,
dupla: bool,
elozo_gesztus_volt: bool,
) -> None:
"""Várja meg a Qt duplakattintási ablakának végét, eseményeket kezelve."""
ora = ora or time.monotonic
alvas = alvas or time.sleep
hatarido = utolso_kattintas + (intervallum_ms + tartalek_ms) / 1000
while True:
app.processEvents()
hatralevo = hatarido - ora()
if hatralevo <= 0:
return
alvas(min(0.05, hatralevo))
"""Valós Qt-időbélyeggel választja el az egérgesztusokat.

A QTest saját eseményórát léptet; a falióra szerinti várakozás nem hat rá.
Egy előző gesztus után ezért egy, a duplakattintási ablaknál hosszabb
időbélyegű egérmozgás zárja le a korábbi kattintást. A dupla kattintás
négy eseményén belül rövid, explicit késleltetés marad.
"""
if elozo_gesztus_volt:
from PySide6.QtCore import QPoint

qtest.mouseMove(
window, point + QPoint(1, 0), delay=double_click_interval_ms + 1
)
fuggveny = qtest.mouseDClick if dupla else qtest.mouseClick
fuggveny(
window,
button,
pos=point,
delay=QTEST_EGER_DELAY_MS,
)


def main(work_dir: Path) -> None:
import picasapy.app.application as app_module
from picasapy.app.controller import AppController
from picasapy.app.edit_controller import EditController
from picasapy.app.edit_preview import EditPreviewProvider
from picasapy.app.effect_thumbnails import EffectThumbnailProvider
from picasapy.app.fileops_controller import FileOpsController
from picasapy.app.thumbnail_provider import ThumbnailProvider
from picasapy.index import open_index, sync_tree
Expand All @@ -54,7 +69,6 @@ def main(work_dir: Path) -> None:
from support.jpeg_factory import make_jpeg

app = QGuiApplication([])
utolso_kattintas_ideje = None

lib = work_dir / "kepek"
lib.mkdir()
Expand All @@ -68,6 +82,9 @@ def main(work_dir: Path) -> None:
str(work_dir / "settings.ini"), QSettings.Format.IniFormat
)
provider = ThumbnailProvider(ThumbnailCache(work_dir / "thumbs", size=32))
effect_thumb_provider = EffectThumbnailProvider(
provider.photo_record, max_threads=1
)
controller = AppController(db, (str(lib),), provider, settings=settings)
edit_preview = EditPreviewProvider()
edit_controller = EditController(edit_preview)
Expand All @@ -76,19 +93,25 @@ def main(work_dir: Path) -> None:
engine = QQmlApplicationEngine()
engine.addImageProvider("thumbs", provider)
engine.addImageProvider("editpreview", edit_preview)
engine.addImageProvider("effectthumb", effect_thumb_provider)
engine.addImportPath(str(app_module._APP_DIR / "qml"))
engine.rootContext().setContextProperty("controller", controller)
engine.rootContext().setContextProperty("editController", edit_controller)
engine.rootContext().setContextProperty("discoveryController", None)
engine.rootContext().setContextProperty("timelineController", None)
engine.rootContext().setContextProperty(
"fileOpsController", fileops_controller
)
engine.rootContext().setContextProperty("appVersion", version_string())
print("PROBE-INIT before Main.qml load", flush=True)
engine.load(str(app_module._APP_DIR / "qml" / "Main.qml"))
print("PROBE-INIT after Main.qml load", flush=True)
assert engine.rootObjects(), "Main.qml betöltése sikertelen"
window = engine.rootObjects()[0]
controller._reload()
controller.selectFolder(str(lib))
app.processEvents()
print("PROBE-INIT after initial processEvents", flush=True)

def child(name):
obj = window.findChild(QObject, name)
Expand Down Expand Up @@ -144,87 +167,87 @@ def child(name):
f"váratlan idő-címke: {time_label.property('text')!r}"
)

def kattintas(item, dupla=False):
nonlocal utolso_kattintas_ideje
if utolso_kattintas_ideje is not None:
varj_kattintasablak_vegere(
app,
utolso_kattintas_ideje,
QGuiApplication.styleHints().mouseDoubleClickInterval(),
)
pont = item.mapToScene(
QPointF(item.property("width") / 2, item.property("height") / 2)
).toPoint()
if dupla:
QTest.mouseDClick(window, Qt.MouseButton.LeftButton, pos=pont)
else:
QTest.mouseClick(window, Qt.MouseButton.LeftButton, pos=pont)
app.processEvents()
utolso_kattintas_ideje = time.monotonic()

def naplozott_dupla_kattintas(item):
nonlocal utolso_kattintas_ideje
if utolso_kattintas_ideje is not None:
varj_kattintasablak_vegere(
app,
utolso_kattintas_ideje,
QGuiApplication.styleHints().mouseDoubleClickInterval(),
)
pont = item.mapToScene(
QPointF(item.property("width") / 2, item.property("height") / 2)
).toPoint()
aktiv_lepes = {"nev": "előkészítés", "elem": None}

def elem_tulajdonsag(elem, nev):
if elem is None:
return None
try:
return elem.property(nev)
except RuntimeError:
return "<destroyed>"

def elem_nev(elem):
if elem is None:
return None
try:
return elem.objectName()
except RuntimeError:
return "<destroyed>"

def nezo_allapot(elem=None):
return (
f"viewerOpen={window.property('viewerOpen')!r}, "
f"currentIndex={viewer.property('currentIndex')!r}, "
f"isCurrentVideo={viewer.property('isCurrentVideo')!r}, "
f"singleClickExit={controller.singleClickExitEnabled!r}, "
"exitAfterDoubleClick="
f"{elem_tulajdonsag(elem, 'exitAfterDoubleClick')!r}, "
f"clickTarget={elem_nev(elem)!r}, "
f"targetEnabled={elem_tulajdonsag(elem, 'enabled')!r}, "
f"targetVisible={elem_tulajdonsag(elem, 'visible')!r}"
)

def naploz(nev):
def allapot(*_args):
class QtEsemenyNaplozo(QObject):
def eventFilter(self, _cel, event):
nev = {
QEvent.Type.MouseButtonPress: "press",
QEvent.Type.MouseButtonRelease: "release",
QEvent.Type.MouseButtonDblClick: "double-click",
QEvent.Type.MouseMove: "move",
}.get(event.type())
if nev is not None:
elem = aktiv_lepes["elem"]
print(
"SingleClickExit állóképes dupla kattintás "
f"MouseArea.{nev} után: "
f"viewerOpen={window.property('viewerOpen')!r}, "
"exitAfterDoubleClick="
f"{item.property('exitAfterDoubleClick')!r}",
"PROBE-MOUSE "
f"step={aktiv_lepes['nev']} event={nev} "
f"timestamp={event.timestamp()} "
f"monotonic={time.monotonic():.6f} "
f"{nezo_allapot(elem)}",
flush=True,
)

return allapot

class QtEsemenyNaplozo(QObject):
def eventFilter(self, _cel, event):
nev = {
QEvent.Type.MouseButtonPress: "press",
QEvent.Type.MouseButtonRelease: "release",
QEvent.Type.MouseButtonDblClick: "double-click",
}.get(event.type())
if nev is not None:
print(
"SingleClickExit Qt egéresemény érkezett "
f"({nev}); viewerOpen="
f"{window.property('viewerOpen')!r}, "
"exitAfterDoubleClick="
f"{item.property('exitAfterDoubleClick')!r}",
flush=True,
)
return False

item.pressed.connect(naploz("pressed"))
item.released.connect(naploz("released"))
item.doubleClicked.connect(naploz("doubleClicked"))
esemenynaplozo = QtEsemenyNaplozo(window)
window.installEventFilter(esemenynaplozo)
QTest.mouseDClick(window, Qt.MouseButton.LeftButton, pos=pont)
app.processEvents()
utolso_kattintas_ideje = time.monotonic()
window.removeEventFilter(esemenynaplozo)
print(
"SingleClickExit állóképes dupla kattintás után: "
f"viewerOpen={window.property('viewerOpen')!r}, "
"exitAfterDoubleClick="
f"{item.property('exitAfterDoubleClick')!r}",
flush=True,
)
return False

esemenynaplozo = QtEsemenyNaplozo(window)
window.installEventFilter(esemenynaplozo)
elozo_gesztus_volt = False

def naplozott_kattintas(nev, elem, *, dupla=False):
nonlocal elozo_gesztus_volt
aktiv_lepes.update(nev=nev, elem=elem)
print(f"PROBE-STEP START {nev} {nezo_allapot(elem)}", flush=True)
try:
pont = elem.mapToScene(
QPointF(
elem.property("width") / 2,
elem.property("height") / 2,
)
).toPoint()
qtest_egerlepes(
QTest,
window,
pont,
Qt.MouseButton.LeftButton,
QGuiApplication.styleHints().mouseDoubleClickInterval(),
dupla=dupla,
elozo_gesztus_volt=elozo_gesztus_volt,
)
app.processEvents()
elozo_gesztus_volt = True
finally:
print(f"PROBE-STEP END {nev} {nezo_allapot(elem)}", flush=True)

def varj(feltetel, timeout_s=3.0):
import time

hatarido = time.monotonic() + timeout_s
while time.monotonic() < hatarido:
app.processEvents()
Expand All @@ -237,11 +260,11 @@ def varj(feltetel, timeout_s=3.0):
assert controller.singleClickExitEnabled is False
video_viewport = item.findChild(QObject, "videoViewport")
assert video_viewport is not None
kattintas(video_viewport)
naplozott_kattintas("video-single-click", video_viewport)
assert window.property("viewerOpen") is True, (
"alapállapotban az egyszeres videókattintás bezárta a szerkesztőt"
)
kattintas(video_viewport, dupla=True)
naplozott_kattintas("video-double-click", video_viewport, dupla=True)
assert varj(lambda: window.property("viewerOpen") is False), (
"alapállapotban a videóablak dupla kattintása nem tért vissza a könyvtárba"
)
Expand All @@ -254,7 +277,7 @@ def varj(feltetel, timeout_s=3.0):
video_item = child("videoLoader").property("item")
video_viewport = video_item.findChild(QObject, "videoViewport")
assert video_viewport is not None
kattintas(video_viewport)
naplozott_kattintas("video-single-click-exit-click", video_viewport)
assert varj(lambda: window.property("viewerOpen") is False), (
"a SingleClickExit bekapcsolva nem vitte vissza a könyvtárba"
)
Expand All @@ -269,7 +292,7 @@ def varj(feltetel, timeout_s=3.0):
assert varj(lambda: pan_area.property("enabled")), (
"a nagyított állóképes előnézet kattintási területe nem aktív"
)
kattintas(pan_area)
naplozott_kattintas("photo-single-click-exit-click", pan_area)
assert varj(lambda: window.property("viewerOpen") is False), (
"a SingleClickExit bekapcsolva az állóképes egyszeres kattintásra "
"nem tért vissza a könyvtárba"
Expand All @@ -283,7 +306,9 @@ def varj(feltetel, timeout_s=3.0):
viewer.setProperty("zoomValue", 1.0)
pan_area = child("viewerPanArea")
assert varj(lambda: pan_area.property("enabled"))
naplozott_dupla_kattintas(pan_area)
naplozott_kattintas(
"photo-double-click-exit-click", pan_area, dupla=True
)
assert varj(lambda: window.property("viewerOpen") is False), (
"SingleClickExit mellett a dupla kattintás után újranyílt vagy "
"nyitva maradt az állóképes néző"
Expand Down
49 changes: 40 additions & 9 deletions tests/app/test_qml_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"""

import os
import re
import socket
import subprocess
import sys
Expand Down Expand Up @@ -70,25 +71,55 @@ def test_video_viewer_probe(tmp_path):
)
if not _linux_hangkimenet_elerheto():
pytest.skip(
"a Qt Multimedia-próba kihagyva: Linuxon sem a PipeWire, sem a "
"PulseAudio kimeneti foglalata nem elérhető ebben a környezetben"
"a Qt Multimedia videópróba kihagyva: a MediaPlayer a "
"videónéző betöltésekor Linuxon hangkimenetet nyit; hang nélküli "
"gépen a videó-gesztust a VideoExitGestureArea külön próbája méri"
)
probe = Path(__file__).parent / "qml_video_probe.py"
repo_root = Path(__file__).resolve().parents[2]
env = dict(os.environ)
env["QT_QPA_PLATFORM"] = "offscreen"
env["QT_QUICK_BACKEND"] = "software"
env["PYTHONPATH"] = os.pathsep.join(
[str(repo_root / "src"), str(repo_root / "tests")]
)
result = subprocess.run(
[sys.executable, str(probe), str(tmp_path)],
capture_output=True,
text=True, encoding="utf-8", errors="replace",
timeout=120,
env=env,
)
try:
result = subprocess.run(
[sys.executable, str(probe), str(tmp_path)],
capture_output=True,
text=True, encoding="utf-8", errors="replace",
timeout=120,
env=env,
)
except subprocess.TimeoutExpired as exc:
def szoveg(kimenet):
if isinstance(kimenet, bytes):
return kimenet.decode("utf-8", errors="replace")
return kimenet or ""

pytest.fail(
"a videónéző-próba 120 másodperc alatt nem fejeződött be\n"
f"stdout:\n{szoveg(exc.stdout)}\n"
f"stderr:\n{szoveg(exc.stderr)}",
pytrace=False,
)
assert result.returncode == 0, (
f"probe exit={result.returncode}\n"
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
)
assert "OK" in result.stdout
lepesek = (
"video-single-click",
"video-double-click",
"video-single-click-exit-click",
"photo-single-click-exit-click",
"photo-double-click-exit-click",
)
for lepes in lepesek:
assert f"PROBE-STEP START {lepes} " in result.stdout
assert f"PROBE-STEP END {lepes} " in result.stdout
assert re.search(
rf"PROBE-MOUSE step={re.escape(lepes)} "
r"event=press timestamp=\d+ monotonic=\d+\.\d+",
result.stdout,
), f"{lepes}: nem került egéresemény-időbélyeg a próbanaplóba"
Loading
Loading