Mercurial > libervia-web
comparison tests/browser/unit/test_chat.py @ 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 | |
children |
comparison
equal
deleted
inserted
replaced
1636:691f6c8afb31 | 1637:29dd52585984 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia Web | |
4 # Copyright (C) 2009-2025 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 from collections import defaultdict | |
20 import sys | |
21 import pytest | |
22 from unittest.mock import MagicMock, AsyncMock, patch | |
23 | |
24 # We mock all non-stdlib modules, so the module can be imported. | |
25 sys.modules["bridge"] = MagicMock(AsyncBridge=AsyncMock) | |
26 sys.modules["cache"] = MagicMock(cache=MagicMock()) | |
27 sys.modules["dialog"] = MagicMock(Confirm=MagicMock()) | |
28 sys.modules["errors"] = MagicMock() | |
29 sys.modules["file_uploader"] = MagicMock(FileUploader=MagicMock()) | |
30 sys.modules["javascript"] = MagicMock() | |
31 sys.modules["jid"] = MagicMock(JID=MagicMock()) | |
32 sys.modules["jid_search"] = MagicMock(JidSearch=MagicMock()) | |
33 sys.modules["loading"] = MagicMock() | |
34 sys.modules["popup"] = MagicMock(create_popup=MagicMock()) | |
35 sys.modules["js_modules"] = MagicMock() | |
36 sys.modules["js_modules.emoji_picker_element"] = MagicMock() | |
37 sys.modules["js_modules.tippy_js"] = MagicMock() | |
38 sys.modules["js_modules.quill"] = MagicMock(Quill=MagicMock()) | |
39 sys.modules["tools"] = MagicMock() | |
40 sys.modules["interpreter"] = MagicMock(Inspector=MagicMock()) | |
41 sys.modules["components"] = MagicMock(init_collapsible_cards=MagicMock()) | |
42 | |
43 | |
44 class FakeElement: | |
45 """FakeElement, with methods to similuate child appending and some attributes.""" | |
46 | |
47 scrollHeight = 0 | |
48 scrollTop = 0 | |
49 clientHeight = 0 | |
50 | |
51 def __init__(self): | |
52 self.attrs = defaultdict(MagicMock) | |
53 self.children = [] | |
54 | |
55 def __getattr__(self, attr) -> MagicMock: | |
56 return self.attrs[attr] | |
57 | |
58 def __le__(self, child): | |
59 child.parent = self | |
60 self.children.append(child) | |
61 return True | |
62 | |
63 | |
64 class FakeDocument: | |
65 | |
66 def __init__(self): | |
67 self.attrs = defaultdict(MagicMock) | |
68 self.items = defaultdict(FakeElement) | |
69 | |
70 def __getattr__(self, attr) -> MagicMock: | |
71 return self.attrs[attr] | |
72 | |
73 def __getitem__(self, item) -> FakeElement: | |
74 return self.items[item] | |
75 | |
76 def __setitem__(self, key, item) -> None: | |
77 self.items[key] = item | |
78 | |
79 | |
80 document = FakeDocument() | |
81 browser = sys.modules["browser"] = MagicMock( | |
82 aio=MagicMock(), | |
83 console=MagicMock(), | |
84 document=document, | |
85 window=MagicMock(), | |
86 navigator=MagicMock(mediaDevices=MagicMock(getDisplayMedia=MagicMock())), | |
87 ) | |
88 | |
89 | |
90 class FakeTemplate(MagicMock): | |
91 | |
92 def get_elt(self, *args, **kwargs): | |
93 return FakeElement() | |
94 | |
95 | |
96 sys.modules["template"] = MagicMock(Template=FakeTemplate) | |
97 | |
98 # We can now import the actual module | |
99 from libervia.web.pages.chat import _browser as chat_module | |
100 | |
101 | |
102 class TestLiberviaWebChat: | |
103 @pytest.fixture | |
104 def chat(self) -> chat_module.LiberviaWebChat: | |
105 """Fixture to create a LiberviaWebChat instance with mocks.""" | |
106 chat = chat_module.LiberviaWebChat() | |
107 | |
108 chat.rich_editor = MagicMock( | |
109 getSelection=MagicMock(return_value=MagicMock(index=0, length=1)), | |
110 getFormat=MagicMock(return_value={}), | |
111 format=MagicMock(), | |
112 getContents=MagicMock(return_value={"ops": []}), | |
113 ) | |
114 | |
115 return chat | |
116 | |
117 @pytest.fixture | |
118 def bridge(self, monkeypatch) -> AsyncMock: | |
119 bridge = AsyncMock() | |
120 monkeypatch.setattr(chat_module, "bridge", bridge) | |
121 return bridge | |
122 | |
123 @pytest.fixture | |
124 def message_elt(self) -> MagicMock: | |
125 """Fixture to create a mock message element.""" | |
126 msg_mock = MagicMock() | |
127 msg_mock.__getitem__.side_effect = lambda key: "msg123" if key == "id" else None | |
128 return msg_mock | |
129 | |
130 @pytest.mark.asyncio | |
131 async def test_forward_action(self, chat, bridge, message_elt) -> None: | |
132 """Forward action launches bridge.message_forward with correct parameters.""" | |
133 message_elt.classList.contains.return_value = True | |
134 with patch("browser.window.prompt", return_value="recipient@example.org"): | |
135 await chat.on_action_forward(None, message_elt) | |
136 | |
137 bridge.message_forward.assert_awaited_once_with("msg123", "recipient@example.org") | |
138 | |
139 @pytest.mark.asyncio | |
140 async def test_rich_editor_actions(self, chat) -> None: | |
141 """Rich editor formatting actions modify editor state correctly.""" | |
142 test_cases = [ | |
143 ("bold", ["bold", True]), | |
144 ("italic", ["italic", True]), | |
145 ("underline", ["underline", True]), | |
146 ("link", ["link", "https://example.org"]), | |
147 ] | |
148 | |
149 for action, expected_call in test_cases: | |
150 evt = MagicMock() | |
151 evt.currentTarget.dataset = {"action": action} | |
152 | |
153 if action == "link": | |
154 with patch("browser.window.prompt", return_value="https://example.org"): | |
155 chat.on_rich_action(evt) | |
156 else: | |
157 chat.on_rich_action(evt) | |
158 | |
159 chat.rich_editor.format.assert_called_with(*expected_call) | |
160 | |
161 @pytest.mark.asyncio | |
162 async def test_extra_recipients(self, monkeypatch, chat) -> None: | |
163 """Adding extra recipient calls "insertBefore".""" | |
164 toolbar_elt = MagicMock() | |
165 monkeypatch.setitem(document, "rich-edit-toolbar", toolbar_elt) | |
166 | |
167 await chat.on_action_extra_recipients(selected="to") | |
168 await chat.on_action_extra_recipients(selected="cc") | |
169 | |
170 assert toolbar_elt.parent.insertBefore.call_count == 2 |