Mercurial > libervia-web
changeset 1637:29dd52585984 default tip
tests (browser/unit/chat): Add tests for forwarding, rich editing and extra recipients:
fix 461
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 04 Jul 2025 18:15:48 +0200 |
parents | 691f6c8afb31 |
children | |
files | tests/browser/unit/test_chat.py |
diffstat | 1 files changed, 170 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/browser/unit/test_chat.py Fri Jul 04 18:15:48 2025 +0200 @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 + +# Libervia Web +# Copyright (C) 2009-2025 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from collections import defaultdict +import sys +import pytest +from unittest.mock import MagicMock, AsyncMock, patch + +# We mock all non-stdlib modules, so the module can be imported. +sys.modules["bridge"] = MagicMock(AsyncBridge=AsyncMock) +sys.modules["cache"] = MagicMock(cache=MagicMock()) +sys.modules["dialog"] = MagicMock(Confirm=MagicMock()) +sys.modules["errors"] = MagicMock() +sys.modules["file_uploader"] = MagicMock(FileUploader=MagicMock()) +sys.modules["javascript"] = MagicMock() +sys.modules["jid"] = MagicMock(JID=MagicMock()) +sys.modules["jid_search"] = MagicMock(JidSearch=MagicMock()) +sys.modules["loading"] = MagicMock() +sys.modules["popup"] = MagicMock(create_popup=MagicMock()) +sys.modules["js_modules"] = MagicMock() +sys.modules["js_modules.emoji_picker_element"] = MagicMock() +sys.modules["js_modules.tippy_js"] = MagicMock() +sys.modules["js_modules.quill"] = MagicMock(Quill=MagicMock()) +sys.modules["tools"] = MagicMock() +sys.modules["interpreter"] = MagicMock(Inspector=MagicMock()) +sys.modules["components"] = MagicMock(init_collapsible_cards=MagicMock()) + + +class FakeElement: + """FakeElement, with methods to similuate child appending and some attributes.""" + + scrollHeight = 0 + scrollTop = 0 + clientHeight = 0 + + def __init__(self): + self.attrs = defaultdict(MagicMock) + self.children = [] + + def __getattr__(self, attr) -> MagicMock: + return self.attrs[attr] + + def __le__(self, child): + child.parent = self + self.children.append(child) + return True + + +class FakeDocument: + + def __init__(self): + self.attrs = defaultdict(MagicMock) + self.items = defaultdict(FakeElement) + + def __getattr__(self, attr) -> MagicMock: + return self.attrs[attr] + + def __getitem__(self, item) -> FakeElement: + return self.items[item] + + def __setitem__(self, key, item) -> None: + self.items[key] = item + + +document = FakeDocument() +browser = sys.modules["browser"] = MagicMock( + aio=MagicMock(), + console=MagicMock(), + document=document, + window=MagicMock(), + navigator=MagicMock(mediaDevices=MagicMock(getDisplayMedia=MagicMock())), +) + + +class FakeTemplate(MagicMock): + + def get_elt(self, *args, **kwargs): + return FakeElement() + + +sys.modules["template"] = MagicMock(Template=FakeTemplate) + +# We can now import the actual module +from libervia.web.pages.chat import _browser as chat_module + + +class TestLiberviaWebChat: + @pytest.fixture + def chat(self) -> chat_module.LiberviaWebChat: + """Fixture to create a LiberviaWebChat instance with mocks.""" + chat = chat_module.LiberviaWebChat() + + chat.rich_editor = MagicMock( + getSelection=MagicMock(return_value=MagicMock(index=0, length=1)), + getFormat=MagicMock(return_value={}), + format=MagicMock(), + getContents=MagicMock(return_value={"ops": []}), + ) + + return chat + + @pytest.fixture + def bridge(self, monkeypatch) -> AsyncMock: + bridge = AsyncMock() + monkeypatch.setattr(chat_module, "bridge", bridge) + return bridge + + @pytest.fixture + def message_elt(self) -> MagicMock: + """Fixture to create a mock message element.""" + msg_mock = MagicMock() + msg_mock.__getitem__.side_effect = lambda key: "msg123" if key == "id" else None + return msg_mock + + @pytest.mark.asyncio + async def test_forward_action(self, chat, bridge, message_elt) -> None: + """Forward action launches bridge.message_forward with correct parameters.""" + message_elt.classList.contains.return_value = True + with patch("browser.window.prompt", return_value="recipient@example.org"): + await chat.on_action_forward(None, message_elt) + + bridge.message_forward.assert_awaited_once_with("msg123", "recipient@example.org") + + @pytest.mark.asyncio + async def test_rich_editor_actions(self, chat) -> None: + """Rich editor formatting actions modify editor state correctly.""" + test_cases = [ + ("bold", ["bold", True]), + ("italic", ["italic", True]), + ("underline", ["underline", True]), + ("link", ["link", "https://example.org"]), + ] + + for action, expected_call in test_cases: + evt = MagicMock() + evt.currentTarget.dataset = {"action": action} + + if action == "link": + with patch("browser.window.prompt", return_value="https://example.org"): + chat.on_rich_action(evt) + else: + chat.on_rich_action(evt) + + chat.rich_editor.format.assert_called_with(*expected_call) + + @pytest.mark.asyncio + async def test_extra_recipients(self, monkeypatch, chat) -> None: + """Adding extra recipient calls "insertBefore".""" + toolbar_elt = MagicMock() + monkeypatch.setitem(document, "rich-edit-toolbar", toolbar_elt) + + await chat.on_action_extra_recipients(selected="to") + await chat.on_action_extra_recipients(selected="cc") + + assert toolbar_elt.parent.insertBefore.call_count == 2