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