168 lines
6.7 KiB
Python
168 lines
6.7 KiB
Python
import time
|
|
import wave
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
from PyQt6.QtCore import QEvent, QUrl
|
|
from PyQt6.QtMultimedia import QSoundEffect
|
|
from PyQt6.QtWidgets import QApplication
|
|
|
|
from fight_reactions import REACTIONS, write_reaction_sound
|
|
from test_chat import room
|
|
|
|
|
|
@pytest.mark.parametrize('kind', REACTIONS)
|
|
def test_command_dispatch(room, qtbot, kind):
|
|
with qtbot.waitSignal(room.fight_action_requested) as event:
|
|
room.chat_input.setText('/' + kind)
|
|
room.send_chat()
|
|
assert event.args == [{'type': 'reaction', 'kind': kind}]
|
|
assert room.chat_input.text() == ''
|
|
assert room.chat_content_layout.count() == 0 # Wait for the server echo.
|
|
|
|
|
|
@pytest.mark.parametrize('kind', REACTIONS)
|
|
def test_strip_is_native_positioned_and_does_not_steal_focus(room, qtbot, kind):
|
|
room.activateWindow()
|
|
room.chat_input.setFocus()
|
|
qtbot.wait(30)
|
|
room.handle_fight_reaction({'kind': kind, 'username': "<b>O'Brien</b>", 'position': 83})
|
|
strip = room.ringside_strip
|
|
assert strip.isWindow()
|
|
assert strip.isVisible()
|
|
assert strip.headline.text() == REACTIONS[kind][0]
|
|
assert "<b>O'Brien</b>" in strip.caption.text()
|
|
assert '01:23' in strip.metadata.text()
|
|
qtbot.wait(210)
|
|
assert QApplication.focusWidget() is room.chat_input
|
|
video = room.video_frame
|
|
rect = strip.geometry()
|
|
from PyQt6.QtCore import QRect, QPoint
|
|
bounds = QRect(video.mapToGlobal(QPoint(0, 0)), video.size())
|
|
assert bounds.contains(rect)
|
|
room.resize(1150, 650)
|
|
qtbot.wait(50)
|
|
bounds = QRect(video.mapToGlobal(QPoint(0, 0)), video.size())
|
|
assert bounds.contains(strip.geometry())
|
|
QApplication.sendEvent(room, QEvent(QEvent.Type.WindowDeactivate))
|
|
assert not strip.isVisible()
|
|
|
|
|
|
def test_strip_auto_dismiss_and_cleanup(room, qtbot):
|
|
room.activateWindow()
|
|
qtbot.wait(30)
|
|
room.handle_fight_reaction({'kind': 'ko', 'username': 'Alex', 'position': 0})
|
|
assert room.ringside_strip.isVisible()
|
|
room.ringside_strip.dismiss_timer.start(30)
|
|
qtbot.waitUntil(lambda: not room.ringside_strip.isVisible())
|
|
room.handle_fight_reaction({'kind': 'bell', 'username': 'Alex', 'position': 0})
|
|
room.cleanup()
|
|
assert not room.ringside_strip.isVisible()
|
|
assert not room.ringside_strip.dismiss_timer.isActive()
|
|
assert not room._fight_poll_timer.isActive()
|
|
|
|
|
|
def test_reactions_work_with_chat_popout_focused(room, qtbot):
|
|
room.popout_chat()
|
|
popout = room.popout_window
|
|
qtbot.addWidget(popout)
|
|
popout.activateWindow()
|
|
room.chat_input.setFocus()
|
|
qtbot.wait(50)
|
|
room.handle_fight_reaction({'kind': 'ko', 'username': 'Other', 'position': 0})
|
|
assert room.ringside_strip.isVisible()
|
|
assert QApplication.focusWidget() is room.chat_input
|
|
popout.close()
|
|
|
|
|
|
def test_arcade_ko_covers_video_then_restores_strip(room, qtbot):
|
|
room.activateWindow()
|
|
qtbot.wait(30)
|
|
room.handle_fight_reaction({'kind': 'ko', 'position': 0})
|
|
overlay = room.ringside_strip
|
|
assert overlay.size() == room.video_frame.size()
|
|
assert overlay.panel.isHidden()
|
|
assert not overlay.ko_canvas.isHidden()
|
|
assert overlay.ko_animation.state() == overlay.ko_animation.State.Running
|
|
room.handle_fight_reaction({'kind': 'bell', 'position': 0})
|
|
assert overlay.ko_canvas.isHidden()
|
|
assert not overlay.panel.isHidden()
|
|
assert overlay.width() <= 540
|
|
assert overlay.height() < room.video_frame.height()
|
|
room.cleanup()
|
|
assert overlay.ko_animation.state() == overlay.ko_animation.State.Stopped
|
|
|
|
|
|
def test_sound_is_enabled_by_default_and_background_effects_are_suppressed(room, qtbot, monkeypatch):
|
|
play = Mock()
|
|
monkeypatch.setattr(room, '_play_reaction_sound', play)
|
|
room.activateWindow()
|
|
qtbot.wait(30)
|
|
room.handle_fight_reaction({'kind': 'ko', 'position': 0})
|
|
play.assert_called_once_with('ko')
|
|
play.reset_mock()
|
|
room.handle_fight_reaction({'kind': 'bell', 'position': 0})
|
|
play.assert_called_once_with('bell')
|
|
room.hide()
|
|
room.handle_fight_reaction({'kind': 'tap', 'position': 0})
|
|
play.assert_called_once_with('bell')
|
|
assert not room.ringside_strip.isVisible()
|
|
|
|
|
|
@pytest.mark.parametrize('kind', REACTIONS)
|
|
def test_original_sound_files_are_valid_and_loadable(qtbot, tmp_path, kind):
|
|
path = write_reaction_sound(tmp_path, kind)
|
|
assert write_reaction_sound(tmp_path, kind) == path
|
|
with wave.open(str(path)) as audio:
|
|
assert audio.getnchannels() == 1
|
|
assert audio.getsampwidth() == 2
|
|
assert audio.getframerate() == 22050
|
|
assert .35 <= audio.getnframes() / 22050 <= .9
|
|
assert any(audio.readframes(audio.getnframes()))
|
|
sound = QSoundEffect()
|
|
sound.setSource(QUrl.fromLocalFile(str(path)))
|
|
qtbot.waitUntil(lambda: sound.status() in (QSoundEffect.Status.Ready, QSoundEffect.Status.Error))
|
|
assert sound.status() == QSoundEffect.Status.Ready
|
|
|
|
|
|
def test_robbery_vote_controls_expiry_and_reconnect(room, qtbot):
|
|
poll = {'id': 'vote-1', 'active': True, 'expiresAt': time.time() * 1000 + 30000,
|
|
'votes': {'Other': 'yes'}}
|
|
room.update_fight_poll(poll)
|
|
assert not room.fight_poll_panel.isHidden()
|
|
assert 'Absolutely 1' in room.fight_poll_result.text()
|
|
with qtbot.waitSignal(room.fight_action_requested) as event:
|
|
room.fight_no_btn.click()
|
|
assert event.args == [{'type': 'fight_poll_vote', 'id': 'vote-1', 'choice': 'no'}]
|
|
poll['votes']['Me'] = 'no'
|
|
room.update_fight_poll(poll)
|
|
assert 'Fair decision 1' in room.fight_poll_result.text()
|
|
assert 'Your vote is in' in room.fight_poll_result.text()
|
|
room.update_connection_status(False)
|
|
assert not room.fight_yes_btn.isEnabled()
|
|
room.update_connection_status(True)
|
|
assert room.fight_yes_btn.isEnabled()
|
|
poll['expiresAt'] = time.time() * 1000 - 1000
|
|
room.update_fight_poll(poll)
|
|
assert 'Vote closed' in room.fight_poll_title.text()
|
|
assert not room.fight_yes_btn.isEnabled()
|
|
assert not room._fight_poll_timer.isActive()
|
|
room.reset_fight_reactions()
|
|
assert room.fight_poll_panel.isHidden()
|
|
|
|
|
|
def test_styled_vote_controls_fit_and_dismiss_stays_dismissed(room, qtbot):
|
|
from main import VlcSyncApp
|
|
VlcSyncApp.apply_stylesheet(room)
|
|
room.splitter.setSizes([750, 350])
|
|
poll = {'id': 'styled', 'active': True, 'expiresAt': time.time() * 1000 + 30000, 'votes': {}}
|
|
room.update_fight_poll(poll)
|
|
qtbot.wait(50)
|
|
for button in (room.fight_yes_btn, room.fight_no_btn, room.fight_poll_dismiss_btn):
|
|
assert button.width() >= button.fontMetrics().horizontalAdvance(button.text()) + 10
|
|
room.fight_poll_dismiss_btn.click()
|
|
room.update_fight_poll(poll)
|
|
assert room.fight_poll_panel.isHidden()
|
|
room.update_fight_poll({**poll, 'id': 'new-poll'})
|
|
assert not room.fight_poll_panel.isHidden()
|