- Clap detection moved from inline buttons to a Tools menu, plain labels (no more '(whole file)' / '(from cursor)' parentheticals) -- status messages moved to the window's status bar since the dedicated label widget went away with the button row. - ClickableSlider subclass: clicking anywhere along the seek track jumps straight there instead of QSlider's default page-by-a-step behavior; reposition-then-delegate-to-super keeps native drag behavior intact. - Dragging now seeks live via sliderMoved instead of waiting for sliderReleased. - PlayerWidget accepts drag-and-drop of a local file to load it. Verified with real Qt events (QMouseEvent clicks, a QDropEvent, a real mouse click via xdotool opening the Tools menu on an actual running window) rather than just calling the underlying methods directly.
98 lines
3.9 KiB
Python
98 lines
3.9 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, no buttons in the layout
|
|
menu_titles = [a.text() for a in win.menuBar().actions()]
|
|
assert "&Tools" in menu_titles, f"expected a Tools menu, got {menu_titles}"
|
|
assert not hasattr(win, "detect_all_button"), "old button attribute should be gone"
|
|
assert win.detect_all_action.text() == "Detect All Claps"
|
|
assert win.detect_next_action.text() == "Detect Next Clap"
|
|
print("PASS: Tools menu has plain-labeled clap detection actions")
|
|
|
|
# -- 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()
|