Files
WeAreSyncing/scripts/smoke_test_interactions.py
T
ThePendulum eb7c1aa500 Add sync preview, restore Detect All Claps button, reorder markers panel
- Preview Sync button builds a short (8s) lossless clip around the
  marked events with the current offset applied, and plays it in a
  small popup PreviewDialog (its own independent PlayerWidget/mpv
  instance) -- lets you check the fix sounds/looks right before
  committing to a full export. ExportWorker gained optional
  clip_start/clip_duration so both the real export and this preview
  share the same underlying export_synced() call.
- Detect All Claps is back as a button in the Sync markers panel
  (alongside Mark Audio/Video Event), in addition to staying in the
  Tools menu -- both widgets' enabled state are kept in sync.
- Sync markers panel now shows Mark Audio Event before Mark Video
  Event.

The clip-mode branch of ffmpeg_export.py (the -ss/-t-on-both-inputs
path ffdelay's own notes flag as easy to get subtly wrong) hadn't been
covered by the existing roundtrip test, which only exercised full-file
export -- added a clip-mode case there, independently verified via the
same scene-change/silence-detection technique as the other cases:
29.4ms drift after the fix, well under one frame.

All of this was built and verified entirely on a virtual Xvfb display
(DISPLAY=:99) per the user's request to stop interrupting their real
desktop -- real mouse clicks and a real (non-monkeypatched) preview
dialog were exercised there, screenshotted, and confirmed correct,
without ever touching the real screen.
2026-07-26 03:01:06 +02:00

102 lines
4.1 KiB
Python

"""Verifies the four interaction changes requested after the first review:
- clap detection moved to a Tools menu (no more buttons in the layout)
- clicking anywhere on the slider track jumps straight there
- dragging the slider seeks live (via sliderMoved), not just on release
- dropping a file onto the player widget loads it
Drives real Qt events (QMouseEvent, QDropEvent) against the actual
MainWindow/PlayerWidget classes rather than calling internal methods
directly, so this proves the actual event wiring, not just the logic
behind it.
"""
import sys
import time
from pathlib import Path
from PyQt6.QtCore import Qt, QPointF, QUrl, QMimeData
from PyQt6.QtGui import QMouseEvent, QDropEvent
from PyQt6.QtWidgets import QApplication
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import main as app_main
ROOT = Path(__file__).resolve().parent.parent
CLIP = str(ROOT / "test_media" / "sync_test_INSYNC.mp4")
def main():
qapp = QApplication(sys.argv)
win = app_main.MainWindow()
win.show()
qapp.processEvents()
# -- Tools menu has the clap detection actions, AND Detect All Claps is
# also back as a button in the Sync markers panel (both should reflect
# the same enabled state, kept in sync explicitly since they're two
# separate widgets).
menu_titles = [a.text() for a in win.menuBar().actions()]
assert "&Tools" in menu_titles, f"expected a Tools menu, got {menu_titles}"
assert win.detect_all_action.text() == "Detect All Claps"
assert win.detect_next_action.text() == "Detect Next Clap"
assert win.detect_all_button.text() == "Detect All Claps"
assert win.detect_all_action.isEnabled() == win.detect_all_button.isEnabled()
print("PASS: Tools menu has plain-labeled clap detection actions, button back in sync markers panel")
# -- Load via drag-and-drop simulation on the player widget
mime = QMimeData()
mime.setUrls([QUrl.fromLocalFile(CLIP)])
drop_event = QDropEvent(
QPointF(10, 10), Qt.DropAction.CopyAction, mime,
Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier,
QDropEvent.Type.Drop,
)
win.player.dropEvent(drop_event)
qapp.processEvents()
time.sleep(0.3)
qapp.processEvents()
assert win.input_path == CLIP, f"drag-and-drop should have loaded the file, got {win.input_path}"
print(f"PASS: drag-and-drop loaded {Path(win.input_path).name}")
# -- Clicking directly on the slider track (not the handle) jumps there
duration_ms = win.seek_slider.maximum()
assert duration_ms > 0
target_x = win.seek_slider.width() * 0.5
click_pos = QPointF(target_x, win.seek_slider.height() / 2)
press_event = QMouseEvent(
QMouseEvent.Type.MouseButtonPress, click_pos, click_pos,
Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier,
)
before = win.player.get_position()
win.seek_slider.mousePressEvent(press_event)
qapp.processEvents()
time.sleep(0.2)
qapp.processEvents()
after = win.player.get_position()
expected = 0.5 * (duration_ms / 1000.0)
print(f"clicked slider track at 50%: before={before:.3f}s after={after:.3f}s expected~{expected:.3f}s")
assert abs(after - expected) < 0.5, "clicking the track should jump the player near that position"
print("PASS: click-anywhere-on-track seeking works")
# -- Dragging (sliderMoved) seeks live, without waiting for release
win.seek_slider.sliderPressed.emit()
qapp.processEvents()
drag_value = int(0.2 * duration_ms)
win.seek_slider.setValue(drag_value)
win.seek_slider.sliderMoved.emit(drag_value)
qapp.processEvents()
time.sleep(0.2)
qapp.processEvents()
live_pos = win.player.get_position()
expected_drag = drag_value / 1000.0
print(f"dragged to 20% (still 'pressed', not released): live player position={live_pos:.3f}s expected~{expected_drag:.3f}s")
assert abs(live_pos - expected_drag) < 0.5, "dragging should seek immediately, before release"
win.seek_slider.sliderReleased.emit()
print("PASS: live-seek during drag works")
win.player.shutdown()
print("\nALL INTERACTION SMOKE TESTS PASSED")
if __name__ == "__main__":
main()