From 683f14b9ca743fc6b083cf3fd06ecbc6c4a0ea2f Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Tue, 30 Jun 2026 22:00:47 -0700 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=A5=20Feature:=20Add=20space.broad?= =?UTF-8?q?cast()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- matrix/space.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/matrix/space.py b/matrix/space.py index c182902..1565b0d 100644 --- a/matrix/space.py +++ b/matrix/space.py @@ -1,5 +1,9 @@ +import asyncio + +from matrix.message import Message from matrix.room import Room, make_room +from matrix.types import File class Space(Room, room_type="m.space"): def get_children(self) -> list[Room]: @@ -27,3 +31,37 @@ def get_children(self) -> list[Room]: children.append(make_room(matrix_room, self._client)) return children + + async def broadcast( + self, + content: str | None = None, + *, + raw: bool = False, + notice: bool = False, + file: File | None = None, + ) -> list[Message]: + """Broadcasts a message to the room. + + Supports text messages (with optional markdown formatting) + and file uploads (including images, videos, and audio). + + ## Example + + ```python + # Broadcast a markdown-formatted text message + await space.broadcast("Hello **world**!") + + # Broadcast a notice message + await space.broadcast("Event started", notice=True) + + # Broadcast a file + file = File(path="mxc://...", filename="document.pdf", mimetype="application/pdf") + await space.broadcast(file=file) + + # Broadcast an image + image = Image(path="mxc://...", filename="photo.jpg", mimetype="image/jpeg", width=800, height=600) + await space.broadcast(file=image) + ``` + """ + async_send = [room.send(content, raw=raw, notice=notice, file=file) for room in self.get_children()] + return await asyncio.gather(*async_send) From 6645f52f809b51b5eaa0946415531bf5af3a7542 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Wed, 1 Jul 2026 20:37:29 -0700 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=9A=A8=20Test:=20Add=20space.broadcas?= =?UTF-8?q?t()=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_space.py | 155 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/tests/test_space.py b/tests/test_space.py index f171ff9..6c4cd60 100644 --- a/tests/test_space.py +++ b/tests/test_space.py @@ -1,8 +1,9 @@ import pytest -from unittest.mock import AsyncMock, Mock -from nio import MatrixRoom +from unittest.mock import AsyncMock, Mock, MagicMock +from nio import MatrixRoom, Event from matrix.room import Room from matrix.space import Space +from matrix.message import Message @pytest.fixture @@ -23,6 +24,23 @@ def space(matrix_space, client): return Space(matrix_space, client) +@pytest.fixture +def mock_send_response(client): + """Set up client to return a mock event after room_send and fetch_event.""" + send_response = Mock() + send_response.event_id = "$event123" + client.room_send = AsyncMock(return_value=send_response) + + mock_event = MagicMock(spec=Event) + mock_event.event_id = "$event123" + + get_event_response = Mock() + get_event_response.event = mock_event + client.room_get_event = AsyncMock(return_value=get_event_response) + + return send_response + + def test_get_children__with_room_child__expect_room_instance( space, matrix_space, client ): @@ -92,3 +110,136 @@ def test_get_children__with_mixed_children__expect_correct_types( types = {r.room_id: type(r) for r in result} assert types["!room:example.com"] is Room assert types["!sub:example.com"] is Space + + +@pytest.mark.asyncio +async def test_broadcast__expect_message_sent_to_all_children( + space, matrix_space, client, mock_send_response +): + child1 = MatrixRoom(room_id="!child1:example.com", own_user_id="@bot:example.com") + child1.name = "Child 1" + child2 = MatrixRoom(room_id="!child2:example.com", own_user_id="@bot:example.com") + child2.name = "Child 2" + matrix_space.children = {"!child1:example.com", "!child2:example.com"} + client.rooms = { + "!child1:example.com": child1, + "!child2:example.com": child2, + } + + results = await space.broadcast("Hello!") + + assert len(results) == 2 + assert all(isinstance(msg, Message) for msg in results) + assert client.room_send.await_count == 2 + + +@pytest.mark.asyncio +async def test_broadcast__with_no_children__expect_empty_list( + space, matrix_space, client +): + matrix_space.children = set() + client.rooms = {} + + results = await space.broadcast("Hello!") + + assert results == [] + client.room_send.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_broadcast__with_unjoined_children__expect_empty_list( + space, matrix_space, client +): + matrix_space.children = {"!unknown:example.com"} + client.rooms = {} + + results = await space.broadcast("Hello!") + + assert results == [] + client.room_send.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_broadcast_raw__expect_unformatted_messages( + space, matrix_space, client, mock_send_response +): + child = MatrixRoom(room_id="!child:example.com", own_user_id="@bot:example.com") + child.name = "Child" + matrix_space.children = {"!child:example.com"} + client.rooms = {"!child:example.com": child} + + await space.broadcast("Hello world!", raw=True) + + call_args = client.room_send.call_args + content = call_args.kwargs["content"] + assert content["msgtype"] == "m.text" + assert content["body"] == "Hello world!" + assert "formatted_body" not in content + + +@pytest.mark.asyncio +async def test_broadcast_notice__expect_notice_message_type( + space, matrix_space, client, mock_send_response +): + child = MatrixRoom(room_id="!child:example.com", own_user_id="@bot:example.com") + child.name = "Child" + matrix_space.children = {"!child:example.com"} + client.rooms = {"!child:example.com": child} + + await space.broadcast("Special Event started!", notice=True) + + call_args = client.room_send.call_args + content = call_args.kwargs["content"] + assert content["msgtype"] == "m.notice" + assert content["body"] == "Special Event started!" + + +@pytest.mark.asyncio +async def test_broadcast_file__expect_file_message( + space, matrix_space, client, mock_send_response +): + from matrix.types import File + + child = MatrixRoom(room_id="!child:example.com", own_user_id="@bot:example.com") + child.name = "Child" + matrix_space.children = {"!child:example.com"} + client.rooms = {"!child:example.com": child} + + file = File( + path="mxc://example.com/abc123", + filename="document.pdf", + mimetype="application/pdf", + ) + + await space.broadcast(file=file) + + call_args = client.room_send.call_args + content = call_args.kwargs["content"] + assert content["msgtype"] == "m.file" + assert content["body"] == "document.pdf" + assert content["url"] == "mxc://example.com/abc123" + + +@pytest.mark.asyncio +async def test_broadcast__with_mixed_children__expect_message_to_all( + space, matrix_space, client, mock_send_response +): + room_child = MatrixRoom(room_id="!room:example.com", own_user_id="@bot:example.com") + room_child.name = "Room Child" + space_child = MatrixRoom(room_id="!sub:example.com", own_user_id="@bot:example.com") + space_child.name = "Sub Space" + space_child.room_type = "m.space" + matrix_space.children = {"!room:example.com", "!sub:example.com"} + client.rooms = { + "!room:example.com": room_child, + "!sub:example.com": space_child, + } + + results = await space.broadcast("Hello!") + + assert len(results) == 2 + assert client.room_send.await_count == 2 + sent_room_ids = { + call.kwargs["room_id"] for call in client.room_send.await_args_list + } + assert sent_room_ids == {"!room:example.com", "!sub:example.com"} From 8f7f81f7a494614ef7ac143283a1b595a4f471fe Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Thu, 2 Jul 2026 17:15:18 -0700 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=94=A5=20Feature:=20Add=20depth=20opt?= =?UTF-8?q?ion=20to=20space.broadcast()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- matrix/space.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/matrix/space.py b/matrix/space.py index 6bfbf8e..d13e8c3 100644 --- a/matrix/space.py +++ b/matrix/space.py @@ -55,12 +55,17 @@ async def broadcast( raw: bool = False, notice: bool = False, file: File | None = None, + depth: int = 1 ) -> list[Message]: - """Broadcasts a message to the room. + """Broadcasts a message to all rooms in this space. Supports text messages (with optional markdown formatting) and file uploads (including images, videos, and audio). + Children the bot has not joined are silently omitted. Use `depth` to + recursively broadcast to children of sub-spaces. `depth=1` broadcasts + to direct children only (default). + ## Example ```python @@ -77,7 +82,11 @@ async def broadcast( # Broadcast an image image = Image(path="mxc://...", filename="photo.jpg", mimetype="image/jpeg", width=800, height=600) await space.broadcast(file=image) + + # Broadcast a notice message to space's rooms and the rooms of its subspaces + await space.broadcast("New Announcement", notice=True, depth=2) ``` """ - async_send = [room.send(content, raw=raw, notice=notice, file=file) for room in self.get_children()] + rooms = filter(lambda room: not isinstance(room, Space), self.get_children(depth=depth)) + async_send = [room.send(content, raw=raw, notice=notice, file=file) for room in rooms] return await asyncio.gather(*async_send) From 496e3f76b3c299553468e486230a5718e5d1bc70 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Thu, 2 Jul 2026 17:42:57 -0700 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=9A=A8=20Test(space):=20Consider=20de?= =?UTF-8?q?pth=20in=20space=20related=20test=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_space.py | 137 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/tests/test_space.py b/tests/test_space.py index a84717c..a4972b1 100644 --- a/tests/test_space.py +++ b/tests/test_space.py @@ -293,9 +293,144 @@ async def test_broadcast__with_mixed_children__expect_message_to_all( results = await space.broadcast("Hello!") + assert len(results) == 1 + assert client.room_send.await_count == 1 + sent_room_ids = { + call.kwargs["room_id"] for call in client.room_send.await_args_list + } + assert sent_room_ids == {"!room:example.com"} + + +@pytest.mark.asyncio +async def test_broadcast__with_mixed_children_and_nested_room__expect_only_top_level_room_at_depth_one( + space, matrix_space, client, mock_send_response +): + room_child = MatrixRoom(room_id="!room:example.com", own_user_id="@bot:example.com") + room_child.name = "Room Child" + space_child = MatrixRoom(room_id="!sub:example.com", own_user_id="@bot:example.com") + space_child.name = "Sub Space" + space_child.room_type = "m.space" + nested = MatrixRoom(room_id="!nested:example.com", own_user_id="@bot:example.com") + nested.name = "Nested Room" + space_child.children = {"!nested:example.com"} + matrix_space.children = {"!room:example.com", "!sub:example.com"} + client.rooms = { + "!room:example.com": room_child, + "!sub:example.com": space_child, + "!nested:example.com": nested, + } + + results = await space.broadcast("Hello!", depth=1) + + assert len(results) == 1 + assert client.room_send.await_count == 1 + sent_room_ids = { + call.kwargs["room_id"] for call in client.room_send.await_args_list + } + assert sent_room_ids == {"!room:example.com"} + + +@pytest.mark.asyncio +async def test_broadcast__with_depth_two__expect_message_to_nested_children( + space, matrix_space, client, mock_send_response +): + subspace = MatrixRoom( + room_id="!subspace:example.com", own_user_id="@bot:example.com" + ) + subspace.name = "Sub Space" + subspace.room_type = "m.space" + nested = MatrixRoom(room_id="!nested:example.com", own_user_id="@bot:example.com") + nested.name = "Nested Room" + subspace.children = {"!nested:example.com"} + matrix_space.children = {"!subspace:example.com"} + client.rooms = { + "!subspace:example.com": subspace, + "!nested:example.com": nested, + } + + results = await space.broadcast("Hello!", depth=2) + + assert len(results) == 1 + assert client.room_send.await_count == 1 + sent_room_ids = { + call.kwargs["room_id"] for call in client.room_send.await_args_list + } + assert sent_room_ids == {"!nested:example.com"} + + +@pytest.mark.asyncio +async def test_broadcast__with_depth_two_and_top_level_room__expect_both_rooms( + space, matrix_space, client, mock_send_response +): + room_child = MatrixRoom(room_id="!room:example.com", own_user_id="@bot:example.com") + room_child.name = "Room Child" + subspace = MatrixRoom( + room_id="!subspace:example.com", own_user_id="@bot:example.com" + ) + subspace.name = "Sub Space" + subspace.room_type = "m.space" + nested = MatrixRoom(room_id="!nested:example.com", own_user_id="@bot:example.com") + nested.name = "Nested Room" + subspace.children = {"!nested:example.com"} + matrix_space.children = {"!room:example.com", "!subspace:example.com"} + client.rooms = { + "!room:example.com": room_child, + "!subspace:example.com": subspace, + "!nested:example.com": nested, + } + + results = await space.broadcast("Hello!", depth=2) + assert len(results) == 2 assert client.room_send.await_count == 2 sent_room_ids = { call.kwargs["room_id"] for call in client.room_send.await_args_list } - assert sent_room_ids == {"!room:example.com", "!sub:example.com"} + assert sent_room_ids == {"!room:example.com", "!nested:example.com"} + + +@pytest.mark.asyncio +async def test_broadcast__with_depth_one__expect_no_nested_children( + space, matrix_space, client, mock_send_response +): + subspace = MatrixRoom( + room_id="!subspace:example.com", own_user_id="@bot:example.com" + ) + subspace.name = "Sub Space" + subspace.room_type = "m.space" + nested = MatrixRoom(room_id="!nested:example.com", own_user_id="@bot:example.com") + nested.name = "Nested Room" + subspace.children = {"!nested:example.com"} + matrix_space.children = {"!subspace:example.com"} + client.rooms = { + "!subspace:example.com": subspace, + "!nested:example.com": nested, + } + + results = await space.broadcast("Hello!", depth=1) + + assert results == [] + client.room_send.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_broadcast__with_depth_zero__expect_no_messages( + space, matrix_space, client, mock_send_response +): + child = MatrixRoom(room_id="!child:example.com", own_user_id="@bot:example.com") + child.name = "Child" + matrix_space.children = {"!child:example.com"} + client.rooms = {"!child:example.com": child} + + results = await space.broadcast("Hello!", depth=0) + + assert results == [] + client.room_send.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_broadcast__with_negative_depth__expect_value_error( + space, matrix_space, client +): + with pytest.raises(ValueError, match="depth must be a non-negative integer"): + await space.broadcast("Hello!", depth=-1) From c2bf1c485b95acdff6fbbb75eae33d7ca9887372 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Thu, 2 Jul 2026 19:07:29 -0700 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=A5=20Feature:=20Add=20bot.broadca?= =?UTF-8?q?st()=20and=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- matrix/bot.py | 42 +++++++++++++++ tests/test_bot.py | 132 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/matrix/bot.py b/matrix/bot.py index 4164f79..ce32301 100644 --- a/matrix/bot.py +++ b/matrix/bot.py @@ -7,6 +7,9 @@ from nio import AsyncClient, Event, MatrixRoom +from matrix.message import Message +from matrix.types import File + from .room import Room, make_room from .space import Space from .group import Group @@ -423,3 +426,42 @@ async def _build_context(self, matrix_room: Room, event: Event) -> Context: ctx.command = cmd return ctx + + # ROOMS + + async def broadcast( + self, + rooms: list[Room], + content: str | None = None, + *, + raw: bool = False, + notice: bool = False, + file: File | None = None, + ) -> list[Message]: + """Broadcasts a message to the specified rooms. + + Supports text messages (with optional markdown formatting) + and file uploads (including images, videos, and audio). + If a space is provided, it is silently skipped. + + ## Example + + ```python + # Broadcast a markdown-formatted text message + await bot.broadcast([room1, room2, ...], "Hello **world**!") + + # Broadcast a notice message + await bot.broadcast([room1, room2, ...], "Event started", notice=True) + + # Broadcast a file + file = File(path="mxc://...", filename="document.pdf", mimetype="application/pdf") + await bot.broadcast([room1, room2, ...], file=file) + + # Broadcast an image + image = Image(path="mxc://...", filename="photo.jpg", mimetype="image/jpeg", width=800, height=600) + await bot.broadcast([room1, room2, ...], file=image) + ``` + """ + rooms = filter(lambda child: not isinstance(child, Space), rooms) + async_send = [room.send(content, raw=raw, notice=notice, file=file) for room in rooms] + return await asyncio.gather(*async_send) diff --git a/tests/test_bot.py b/tests/test_bot.py index 18b7f16..9a7e134 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -1,9 +1,11 @@ import pytest -from unittest.mock import AsyncMock, MagicMock, patch -from nio import MatrixRoom, RoomMessageText, LoginError +from unittest.mock import AsyncMock, MagicMock, Mock, patch +from nio import MatrixRoom, RoomMessageText, LoginError, Event from matrix import Bot, Config, Extension, Room, Space +from matrix.message import Message +from matrix.types import File from matrix.errors import ( CheckError, CommandNotFoundError, @@ -970,3 +972,129 @@ async def task(): job_names = [j.name for j in bot.scheduler.jobs] assert "task" in job_names + + +@pytest.fixture +def mock_send_response(bot): + """Set up client to return a mock event after room_send and fetch_event.""" + send_response = Mock() + send_response.event_id = "$event123" + bot._client.room_send = AsyncMock(return_value=send_response) + + mock_event = MagicMock(spec=Event) + mock_event.event_id = "$event123" + + get_event_response = Mock() + get_event_response.event = mock_event + bot._client.room_get_event = AsyncMock(return_value=get_event_response) + + return send_response + + +@pytest.fixture +def make_room(bot): + """Factory that creates a Room instance for a given room ID.""" + def _make(room_id): + matrix_room = MatrixRoom(room_id=room_id, own_user_id="grace") + matrix_room.name = room_id + bot._client.rooms = {**bot._client.rooms, room_id: matrix_room} + return Room(matrix_room, bot.client) + + return _make + + +@pytest.mark.asyncio +async def test_broadcast__expect_message_sent_to_all_rooms( + bot, make_room, mock_send_response +): + room1 = make_room("!room1:example.com") + room2 = make_room("!room2:example.com") + + results = await bot.broadcast([room1, room2], "Hello!") + + assert len(results) == 2 + assert all(isinstance(msg, Message) for msg in results) + assert bot._client.room_send.await_count == 2 + + +@pytest.mark.asyncio +async def test_broadcast__with_empty_list__expect_no_messages( + bot, mock_send_response +): + results = await bot.broadcast([], "Hello!") + + assert results == [] + bot._client.room_send.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_broadcast__with_space_in_list__expect_space_skipped( + bot, make_room, mock_send_response +): + room = make_room("!room:example.com") + space_matrix = MatrixRoom( + room_id="!space:example.com", own_user_id="grace" + ) + space_matrix.name = "Test Space" + space_matrix.room_type = "m.space" + bot._client.rooms = {**bot._client.rooms, "!space:example.com": space_matrix} + space = Space(space_matrix, bot.client) + + results = await bot.broadcast([room, space], "Hello!") + + assert len(results) == 1 + assert bot._client.room_send.await_count == 1 + sent_room_ids = { + call.kwargs["room_id"] for call in bot._client.room_send.await_args_list + } + assert sent_room_ids == {"!room:example.com"} + + +@pytest.mark.asyncio +async def test_broadcast_raw__expect_unformatted_messages( + bot, make_room, mock_send_response +): + room = make_room("!room:example.com") + + await bot.broadcast([room], "Hello world!", raw=True) + + call_args = bot._client.room_send.call_args + content = call_args.kwargs["content"] + assert content["msgtype"] == "m.text" + assert content["body"] == "Hello world!" + assert "formatted_body" not in content + + +@pytest.mark.asyncio +async def test_broadcast_notice__expect_notice_message_type( + bot, make_room, mock_send_response +): + room = make_room("!room:example.com") + + await bot.broadcast([room], "Special Event started!", notice=True) + + call_args = bot._client.room_send.call_args + content = call_args.kwargs["content"] + assert content["msgtype"] == "m.notice" + assert content["body"] == "Special Event started!" + + +@pytest.mark.asyncio +async def test_broadcast_file__expect_file_message( + bot, make_room, mock_send_response +): + room = make_room("!room:example.com") + + file = File( + path="mxc://example.com/abc123", + filename="document.pdf", + mimetype="application/pdf", + ) + + await bot.broadcast([room], file=file) + + call_args = bot._client.room_send.call_args + content = call_args.kwargs["content"] + assert content["msgtype"] == "m.file" + assert content["body"] == "document.pdf" + assert content["url"] == "mxc://example.com/abc123" From 5688f08b2f68af9b3eace3c72e68684c60a02aa8 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Fri, 3 Jul 2026 19:28:43 -0700 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20Run=20black=20forma?= =?UTF-8?q?tter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- matrix/bot.py | 4 +++- matrix/space.py | 11 ++++++++--- tests/test_bot.py | 13 ++++--------- tests/test_space.py | 1 + 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/matrix/bot.py b/matrix/bot.py index ce32301..af2d3ce 100644 --- a/matrix/bot.py +++ b/matrix/bot.py @@ -463,5 +463,7 @@ async def broadcast( ``` """ rooms = filter(lambda child: not isinstance(child, Space), rooms) - async_send = [room.send(content, raw=raw, notice=notice, file=file) for room in rooms] + async_send = [ + room.send(content, raw=raw, notice=notice, file=file) for room in rooms + ] return await asyncio.gather(*async_send) diff --git a/matrix/space.py b/matrix/space.py index d13e8c3..f2babfd 100644 --- a/matrix/space.py +++ b/matrix/space.py @@ -6,6 +6,7 @@ from matrix.types import File + class Space(Room, room_type="m.space"): def get_children(self, depth: int = 1) -> list[Room | Self]: """Return the child rooms and spaces of this space that the bot has joined. @@ -55,7 +56,7 @@ async def broadcast( raw: bool = False, notice: bool = False, file: File | None = None, - depth: int = 1 + depth: int = 1, ) -> list[Message]: """Broadcasts a message to all rooms in this space. @@ -87,6 +88,10 @@ async def broadcast( await space.broadcast("New Announcement", notice=True, depth=2) ``` """ - rooms = filter(lambda room: not isinstance(room, Space), self.get_children(depth=depth)) - async_send = [room.send(content, raw=raw, notice=notice, file=file) for room in rooms] + rooms = filter( + lambda room: not isinstance(room, Space), self.get_children(depth=depth) + ) + async_send = [ + room.send(content, raw=raw, notice=notice, file=file) for room in rooms + ] return await asyncio.gather(*async_send) diff --git a/tests/test_bot.py b/tests/test_bot.py index 9a7e134..7747bb8 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -994,6 +994,7 @@ def mock_send_response(bot): @pytest.fixture def make_room(bot): """Factory that creates a Room instance for a given room ID.""" + def _make(room_id): matrix_room = MatrixRoom(room_id=room_id, own_user_id="grace") matrix_room.name = room_id @@ -1018,9 +1019,7 @@ async def test_broadcast__expect_message_sent_to_all_rooms( @pytest.mark.asyncio -async def test_broadcast__with_empty_list__expect_no_messages( - bot, mock_send_response -): +async def test_broadcast__with_empty_list__expect_no_messages(bot, mock_send_response): results = await bot.broadcast([], "Hello!") assert results == [] @@ -1032,9 +1031,7 @@ async def test_broadcast__with_space_in_list__expect_space_skipped( bot, make_room, mock_send_response ): room = make_room("!room:example.com") - space_matrix = MatrixRoom( - room_id="!space:example.com", own_user_id="grace" - ) + space_matrix = MatrixRoom(room_id="!space:example.com", own_user_id="grace") space_matrix.name = "Test Space" space_matrix.room_type = "m.space" bot._client.rooms = {**bot._client.rooms, "!space:example.com": space_matrix} @@ -1080,9 +1077,7 @@ async def test_broadcast_notice__expect_notice_message_type( @pytest.mark.asyncio -async def test_broadcast_file__expect_file_message( - bot, make_room, mock_send_response -): +async def test_broadcast_file__expect_file_message(bot, make_room, mock_send_response): room = make_room("!room:example.com") file = File( diff --git a/tests/test_space.py b/tests/test_space.py index a4972b1..800fb3e 100644 --- a/tests/test_space.py +++ b/tests/test_space.py @@ -168,6 +168,7 @@ def test_get_children__with_depth_two__expect_recursive_children( assert "!subspace:example.com" in ids assert "!nested:example.com" in ids + @pytest.mark.asyncio async def test_broadcast__expect_message_sent_to_all_children( space, matrix_space, client, mock_send_response From f872dc00d05c19d0e861e285c810750edf9923c5 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Fri, 3 Jul 2026 19:47:26 -0700 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20Respect=20mypy=20li?= =?UTF-8?q?nter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- matrix/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix/bot.py b/matrix/bot.py index af2d3ce..5291803 100644 --- a/matrix/bot.py +++ b/matrix/bot.py @@ -462,7 +462,7 @@ async def broadcast( await bot.broadcast([room1, room2, ...], file=image) ``` """ - rooms = filter(lambda child: not isinstance(child, Space), rooms) + rooms = list(filter(lambda child: not isinstance(child, Space), rooms)) async_send = [ room.send(content, raw=raw, notice=notice, file=file) for room in rooms ]