Files
WeAreSyncing/scripts/smoke_test_interactions.py
T
ThePendulum 8c34a0f0c2 Give each tab its own full timeline and transport (PlayerPanel refactor)
Extracts a new PlayerPanel widget (video + its own seek slider/marker
strip + its own centered frame-step/play/mute transport row) into
player_panel.py. Both Source and Preview tabs now use one, so
switching tabs switches a complete, independent monitor -- its own
scrubber, its own play state, its own frame-stepping -- not just the
video frame with a single shared play button underneath.

main.py's global keyboard shortcuts (,/./space/left/right) now
dispatch to whichever panel is active via a small _active_panel()
helper, so they follow the same 'whichever monitor you're looking at'
rule as the on-screen controls.

MarkerStrip (clap detection) and the Mark Audio/Video/offset/export
row stay outside the tabs, at the MainWindow level -- marking and
exporting are inherently about the source file, not the preview clip,
so only the timeline+transport moved, not the whole marking workflow.

Updated every existing smoke test for the new win.source_panel.* /
win.preview_panel.* structure (previously flat win.player,
win.seek_slider, etc.), and added a new check to
smoke_test_preview.py confirming the Preview tab's slider is a
genuinely separate, functional widget -- dragging it seeks the preview
player without moving the source player, which is the actual thing
this change was for.

Verified the full suite on the virtual Xvfb display, plus a live
click-through (mark both events, build preview, confirm the Preview
tab shows its own populated timeline/transport, not the source's).
2026-07-26 03:58:23 +02:00

103 lines
4.3 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.source_panel.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.source_panel.seek_slider.maximum()
assert duration_ms > 0
target_x = win.source_panel.seek_slider.width() * 0.5
click_pos = QPointF(target_x, win.source_panel.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.source_panel.player.get_position()
win.source_panel.seek_slider.mousePressEvent(press_event)
qapp.processEvents()
time.sleep(0.2)
qapp.processEvents()
after = win.source_panel.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.source_panel.seek_slider.sliderPressed.emit()
qapp.processEvents()
drag_value = int(0.2 * duration_ms)
win.source_panel.seek_slider.setValue(drag_value)
win.source_panel.seek_slider.sliderMoved.emit(drag_value)
qapp.processEvents()
time.sleep(0.2)
qapp.processEvents()
live_pos = win.source_panel.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.source_panel.seek_slider.sliderReleased.emit()
print("PASS: live-seek during drag works")
win.source_panel.shutdown()
win.preview_panel.shutdown()
print("\nALL INTERACTION SMOKE TESTS PASSED")
if __name__ == "__main__":
main()