Files
video-sync/desktop-client/test_chat.py
T

220 lines
8.4 KiB
Python

from unittest.mock import Mock
import pytest
from PyQt6.QtCore import QObject, pyqtSignal
from PyQt6.QtGui import QTextDocument
from PyQt6.QtWidgets import QApplication
import room_widget
class PlayerSignals(QObject):
time_changed = pyqtSignal(int)
state_changed = pyqtSignal(bool, int)
@pytest.fixture
def room(qtbot, monkeypatch):
player = Mock(signals=PlayerSignals())
monkeypatch.setattr(room_widget, 'VLCSyncPlayer', lambda frame: player)
monkeypatch.setattr(room_widget.RoomWidget, '_play_reaction_sound', Mock())
widget = room_widget.RoomWidget()
widget.username = 'Me'
qtbot.addWidget(widget)
widget.resize(1050, 500)
widget.show()
return widget
@pytest.mark.parametrize('text', ["It's someone's & my <message>",
"It's at 1:23 & <b>literal</b>",
'line one\nline two', 'literal &#39; entity'])
def test_message_rendering(room, text):
room.add_chat_message("<b>O'Brien</b>", text, 0)
bubble = room.chat_content_layout.itemAt(0).widget()
doc = QTextDocument()
doc.setHtml(bubble.text_lbl.text())
assert doc.toPlainText() == text
if '1:23' in text:
assert 'href="seek:1:23"' in bubble.text_lbl.text()
def test_bubbles_do_not_shrink_or_clip_when_overflowing(room, qtbot):
text = 'A wrapped message with several words. ' * 8
room.add_chat_message('Other', text, 0)
qtbot.wait(100)
first = room.chat_content_layout.itemAt(0).widget()
initial_height = first.height()
for i in range(35):
room.add_chat_message('Me' if i % 2 else 'Other', text, 0)
qtbot.wait(200)
assert room.chat_messages.verticalScrollBar().maximum() > 0
assert first.height() >= initial_height
for width in (220, 450, 280):
room.splitter.setSizes([room.width() - width, width])
qtbot.wait(100)
for i in range(room.chat_content_layout.count()):
bubble = room.chat_content_layout.itemAt(i).widget()
label = bubble.text_lbl
assert label.height() >= label.heightForWidth(label.width())
assert bubble.bubble.rect().contains(label.geometry())
def test_unread_alert_and_focus_clear(room, qtbot, monkeypatch):
alert = Mock()
monkeypatch.setattr(QApplication, 'alert', alert)
room.activateWindow()
room.setFocus()
qtbot.wait(50)
room.add_chat_message('Other', 'hello', 0)
assert room._unread_messages == 1
alert.assert_called_once()
room.add_chat_message('Me', 'reply', 0)
assert room._unread_messages == 1
room.chat_input.setFocus()
qtbot.wait(50)
assert room._unread_messages == 0
room.add_chat_message('Other', 'focused', 0)
assert room._unread_messages == 0
room.chat_container.hide()
room.add_chat_message('Other', 'hidden', 0)
assert room._unread_messages == 1
room.clear_chat()
assert room._unread_messages == 0
def test_popout_focus_and_wrapping(room, qtbot, monkeypatch):
monkeypatch.setattr(QApplication, 'alert', Mock())
room.popout_chat()
popout = room.popout_window
qtbot.addWidget(popout)
popout.activateWindow()
room.chat_input.setFocus()
qtbot.wait(100)
room.add_chat_message('Other', 'Wrapped popout text ' * 40, 0)
qtbot.wait(100)
assert room._unread_messages == 0
bubble = room.chat_content_layout.itemAt(0).widget()
assert bubble.text_lbl.height() >= bubble.text_lbl.heightForWidth(bubble.text_lbl.width())
room.activateWindow()
room.setFocus()
qtbot.wait(100)
room.add_chat_message('Other', 'Background popout', 0)
assert room._unread_messages == 1
popout.close()
def test_scrolling_preserves_position_and_counts_unread(room, qtbot):
for i in range(30):
room.add_chat_message('Other', f'Message {i}: ' + 'wrapped words ' * 10, 0)
qtbot.wait(150)
bar = room.chat_messages.verticalScrollBar()
assert bar.value() == bar.maximum()
bar.setValue(100)
qtbot.wait(30)
position = bar.value()
room.add_chat_message('Other', 'First unread message', 0)
room.add_chat_message('Other', 'Second unread message', 0)
room.add_system_message('Someone paused')
qtbot.wait(150)
assert bar.value() == position
assert room._pending_chat_messages == 2
assert not room.chat_jump_btn.isHidden()
assert room._unread_divider is not None
room.chat_input.setFocus()
assert room._pending_chat_messages == 2
room.jump_to_latest()
qtbot.wait(150)
assert bar.value() == bar.maximum()
assert room.chat_jump_btn.isHidden()
room.add_chat_message('Other', 'Follow latest', 0)
qtbot.wait(150)
assert bar.value() == bar.maximum()
assert room._pending_chat_messages == 0
def test_scroll_back_to_bottom_clears_pending(room, qtbot):
for i in range(20):
room.add_chat_message('Other', 'Words ' * 30, 0)
qtbot.wait(150)
bar = room.chat_messages.verticalScrollBar()
bar.setValue(0)
room.add_chat_message('Other', 'Unread', 0)
qtbot.wait(100)
bar.setValue(bar.maximum())
assert room._pending_chat_messages == 0
assert room._follow_chat
assert room.chat_jump_btn.isHidden()
room.clear_chat()
assert room._unread_divider is None
assert room.chat_content_layout.count() == 0
def test_ready_command_and_controls(room, qtbot):
with qtbot.waitSignal(room.ready_action_requested) as signal:
room.chat_input.setText('/ready')
room.send_chat()
assert signal.args == [{'type': 'ready_check', 'action': 'begin'}]
assert room.chat_input.text() == ''
state = {'active': True, 'id': 'check-1', 'requestedBy': 'Me', 'participants': [
{'username': 'Me', 'ready': False, 'connected': True},
{'username': "<b>O'Brien</b>", 'ready': True, 'connected': True}]}
room.update_ready_check(state)
assert not room.ready_panel.isHidden()
assert not room.ready_start_btn.isEnabled()
assert "<b>O'Brien</b>" in room.ready_users.text()
with qtbot.waitSignal(room.ready_action_requested) as signal:
room.ready_btn.click()
assert signal.args[0] == {'type': 'ready_check', 'action': 'vote', 'id': 'check-1', 'ready': True}
state['participants'][0]['ready'] = True
room.update_ready_check(state)
assert room.ready_start_btn.isEnabled()
with qtbot.waitSignal(room.ready_action_requested) as signal:
room.ready_start_btn.click()
assert signal.args[0]['action'] == 'start'
state['participants'][1]['connected'] = False
room.update_ready_check(state)
assert not room.ready_start_btn.isEnabled()
assert 'Reconnecting' in room.ready_users.text()
state['requestedBy'] = 'Other'
room.update_ready_check(state)
assert room.ready_start_btn.isHidden()
room.update_ready_check({'active': False})
assert room.ready_panel.isHidden()
def test_ready_panel_buttons_fit_styled_chat(room, qtbot):
from main import VlcSyncApp
VlcSyncApp.apply_stylesheet(room)
room.splitter.setSizes([750, 300])
room.update_ready_check({'active': True, 'id': 'layout', 'requestedBy': 'Me', 'participants': [
{'username': 'Me', 'ready': True, 'connected': True}]})
qtbot.wait(100)
for button in (room.ready_btn, room.ready_start_btn, room.ready_cancel_btn):
assert button.width() >= button.fontMetrics().horizontalAdvance(button.text()) + 20
room.update_connection_status(False)
room.update_ready_check(room._ready_check)
assert not room.ready_start_btn.isEnabled()
assert not room.ready_btn.isEnabled()
def test_app_wires_commands_to_websocket_and_incoming_panels(room, qtbot, monkeypatch):
import main
from sync_client import SyncClientThread
client = SyncClientThread()
client.send_message = Mock()
monkeypatch.setattr(main, 'SyncClientThread', lambda url: client)
app = main.VlcSyncApp()
qtbot.addWidget(app)
for command, expected in [('/ready', {'type': 'ready_check', 'action': 'begin'}),
('/ko', {'type': 'reaction', 'kind': 'ko'}),
('/bell', {'type': 'reaction', 'kind': 'bell'})]:
app.room_widget.chat_input.setText(command)
app.room_widget.send_chat()
client.send_message.assert_called_with(expected)
state = {'type': 'ready_check', 'active': True, 'id': 'wired', 'requestedBy': 'Me',
'participants': [{'username': 'Me', 'ready': False, 'connected': True}]}
client._handle_message(state)
assert app.room_widget._ready_check['id'] == 'wired'
assert not app.room_widget.ready_panel.isHidden()