import pytest from aiogram.enums import MessageEntityType from aiogram.types import MessageEntity, User from aiogram.utils.formatting import ( Bold, BotCommand, CashTag, Code, CustomEmoji, Email, HashTag, Italic, PhoneNumber, Pre, Spoiler, Strikethrough, Text, TextLink, TextMention, Underline, Url, _apply_entity, as_key_value, as_line, as_list, as_marked_list, as_marked_section, as_numbered_list, as_numbered_section, as_section, ) from aiogram.utils.text_decorations import html_decoration class TestNode: @pytest.mark.parametrize( "node,result", [ [ Text("test"), "test", ], [ HashTag("#test"), "#test", ], [ CashTag("$TEST"), "$TEST", ], [ BotCommand("/test"), "/test", ], [ Url("https://example.com"), "https://example.com", ], [ Email("test@example.com"), "test@example.com", ], [ PhoneNumber("test"), "test", ], [ Bold("test"), "test", ], [ Italic("test"), "test", ], [ Underline("test"), "test", ], [ Strikethrough("test"), "test", ], [ Spoiler("test"), "test", ], [ Code("test"), "test", ], [ Pre("test", language="python"), '
test
', ], [ TextLink("test", url="https://example.com"), 'test', ], [ TextMention("test", user=User(id=42, is_bot=False, first_name="Test")), 'test', ], [ CustomEmoji("test", custom_emoji_id="42"), 'test', ], ], ) def test_render_plain_only(self, node: Text, result: str): text, entities = node.render() if node.type: assert len(entities) == 1 entity = entities[0] assert entity.type == node.type content = html_decoration.unparse(text, entities) assert content == result def test_render_text(self): node = Text("Hello, ", "World", "!") text, entities = node.render() assert text == "Hello, World!" assert not entities def test_render_nested(self): node = Text( Text("Hello, ", Bold("World"), "!"), "\n", Text(Bold("This ", Underline("is"), " test", Italic("!"))), "\n", HashTag("#test"), ) text, entities = node.render() assert text == "Hello, World!\nThis is test!\n#test" assert entities == [ MessageEntity(type="bold", offset=7, length=5), MessageEntity(type="bold", offset=14, length=13), MessageEntity(type="underline", offset=19, length=2), MessageEntity(type="italic", offset=26, length=1), MessageEntity(type="hashtag", offset=28, length=5), ] def test_as_kwargs_default(self): node = Text("Hello, ", Bold("World"), "!") result = node.as_kwargs() assert "text" in result assert "entities" in result assert "parse_mode" in result def test_as_kwargs_custom(self): node = Text("Hello, ", Bold("World"), "!") result = node.as_kwargs( text_key="caption", entities_key="custom_entities", parse_mode_key="custom_parse_mode", ) assert "text" not in result assert "caption" in result assert "entities" not in result assert "custom_entities" in result assert "parse_mode" not in result assert "custom_parse_mode" in result def test_as_html(self): node = Text("Hello, ", Bold("World"), "!") assert node.as_html() == "Hello, World!" def test_as_markdown(self): node = Text("Hello, ", Bold("World"), "!") assert node.as_markdown() == r"Hello, *World*\!" def test_replace(self): node0 = Text("test0", param0="test1") node1 = node0.replace("test1", "test2", param1="test1") assert node0._body != node1._body assert node0._params != node1._params assert "param1" not in node0._params assert "param1" in node1._params def test_add(self): node0 = Text("Hello") node1 = Bold("World") node2 = node0 + Text(", ") + node1 + "!" assert node0 != node2 assert node1 != node2 assert len(node0._body) == 1 assert len(node1._body) == 1 assert len(node2._body) == 3 text, entities = node2.render() assert text == "Hello, World!" def test_getitem_position(self): node = Text("Hello, ", Bold("World"), "!") with pytest.raises(TypeError): node[2] def test_getitem_empty_slice(self): node = Text("Hello, ", Bold("World"), "!") new_node = node[:] assert new_node is not node assert isinstance(new_node, Text) assert new_node._body == node._body def test_getitem_slice_zero(self): node = Text("Hello, ", Bold("World"), "!") new_node = node[2:2] assert node is not new_node assert isinstance(new_node, Text) assert not new_node._body def test_getitem_slice_simple(self): node = Text("Hello, ", Bold("World"), "!") new_node = node[2:10] assert isinstance(new_node, Text) text, entities = new_node.render() assert text == "llo, Wor" assert len(entities) == 1 assert entities[0].type == MessageEntityType.BOLD def test_getitem_slice_inside_child(self): node = Text("Hello, ", Bold("World"), "!") new_node = node[8:10] assert isinstance(new_node, Text) text, entities = new_node.render() assert text == "or" assert len(entities) == 1 assert entities[0].type == MessageEntityType.BOLD def test_getitem_slice_tail(self): node = Text("Hello, ", Bold("World"), "!") new_node = node[12:13] assert isinstance(new_node, Text) text, entities = new_node.render() assert text == "!" assert not entities def test_from_entities(self): # Most of the cases covered by text_decorations module node = Strikethrough.from_entities( text="test1 test2 test3 test4 test5 test6 test7", entities=[ MessageEntity(type="bold", offset=6, length=29), MessageEntity(type="underline", offset=12, length=5), MessageEntity(type="italic", offset=24, length=5), ], ) assert len(node._body) == 3 assert isinstance(node, Strikethrough) rendered = node.as_html() assert rendered == "test1 test2 test3 test4 test5 test6 test7" def test_pretty_string(self): node = Strikethrough.from_entities( text="X", entities=[ MessageEntity( type=MessageEntityType.CUSTOM_EMOJI, offset=0, length=1, custom_emoji_id="42", ), ], ) assert ( node.as_pretty_string(indent=True) == """Strikethrough( Text( 'X', custom_emoji_id='42' ) )""" ) class TestHashTag: def test_only_one_element_in_body(self): with pytest.raises(ValueError): HashTag("test", "test") def test_body_is_not_str(self): with pytest.raises(ValueError): HashTag(Text("test")) def test_with_no_prefix(self): node = HashTag("test") assert node._body == ("#test",) def test_with_prefix(self): node = HashTag("#test") assert node._body == ("#test",) class TestCashTag: def test_only_one_element_in_body(self): with pytest.raises(ValueError): CashTag("test", "test") def test_body_is_not_str(self): with pytest.raises(ValueError): CashTag(Text("test")) def test_with_no_prefix(self): node = CashTag("USD") assert node._body == ("$USD",) def test_with_prefix(self): node = CashTag("$USD") assert node._body == ("$USD",) class TestUtils: def test_apply_entity(self): node = _apply_entity( MessageEntity(type=MessageEntityType.BOLD, offset=0, length=4), "test" ) assert isinstance(node, Bold) assert node._body == ("test",) def test_as_line(self): node = as_line("test", "test", "test") assert isinstance(node, Text) assert len(node._body) == 4 # 3 + '\n' def test_line_with_sep(self): node = as_line("test", "test", "test", sep=" ") assert isinstance(node, Text) assert len(node._body) == 6 # 3 + 2 * ' ' + '\n' def test_as_line_single_element_with_sep(self): node = as_line("test", sep=" ") assert isinstance(node, Text) assert len(node._body) == 2 # 1 + '\n' def test_as_list(self): node = as_list("test", "test", "test") assert isinstance(node, Text) assert len(node._body) == 5 # 3 + 2 * '\n' between lines def test_as_marked_list(self): node = as_marked_list("test 1", "test 2", "test 3") assert node.as_html() == "- test 1\n- test 2\n- test 3" def test_as_numbered_list(self): node = as_numbered_list("test 1", "test 2", "test 3", start=5) assert node.as_html() == "5. test 1\n6. test 2\n7. test 3" def test_as_section(self): node = as_section("title", "test 1", "test 2", "test 3") assert node.as_html() == "title\ntest 1test 2test 3" def test_as_marked_section(self): node = as_marked_section("Section", "test 1", "test 2", "test 3") assert node.as_html() == "Section\n- test 1\n- test 2\n- test 3" def test_as_numbered_section(self): node = as_numbered_section("Section", "test 1", "test 2", "test 3", start=5) assert node.as_html() == "Section\n5. test 1\n6. test 2\n7. test 3" def test_as_key_value(self): node = as_key_value("key", "test 1") assert node.as_html() == "key: test 1"