diff --git a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextFormattingTest.kt b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextFormattingTest.kt
new file mode 100644
index 0000000000..ff5a77f6d1
--- /dev/null
+++ b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextFormattingTest.kt
@@ -0,0 +1,221 @@
+package dev.inmo.tgbotapi.types.rich
+
+import dev.inmo.tgbotapi.types.ChatId
+import dev.inmo.tgbotapi.types.CustomEmojiId
+import dev.inmo.tgbotapi.types.RawChatId
+import dev.inmo.tgbotapi.types.chat.CommonUser
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class RichTextFormattingTest {
+ @Test
+ fun bold() {
+ val entity = RichTextBold(RichTextPlain("x"))
+ assertEquals("**x**", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun italic() {
+ val entity = RichTextItalic(RichTextPlain("x"))
+ assertEquals("*x*", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun underline() {
+ val entity = RichTextUnderline(RichTextPlain("x"))
+ assertEquals("x", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun strikethrough() {
+ val entity = RichTextStrikethrough(RichTextPlain("x"))
+ assertEquals("~~x~~", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun spoiler() {
+ val entity = RichTextSpoiler(RichTextPlain("x"))
+ assertEquals("||x||", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun subscript() {
+ val entity = RichTextSubscript(RichTextPlain("x"))
+ assertEquals("x", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun superscript() {
+ val entity = RichTextSuperscript(RichTextPlain("x"))
+ assertEquals("x", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun marked() {
+ val entity = RichTextMarked(RichTextPlain("x"))
+ assertEquals("==x==", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun code() {
+ val entity = RichTextCode(RichTextPlain("x"))
+ assertEquals("`x`", entity.markdown)
+ assertEquals("x", entity.html)
+ }
+
+ @Test
+ fun dateTime() {
+ val entity = RichTextDateTime(RichTextPlain("now"), 1647531900L, "wDT")
+ assertEquals("", entity.markdown)
+ assertEquals("now", entity.html)
+ }
+
+ @Test
+ fun textMention() {
+ val entity = RichTextTextMention(RichTextPlain("John"), CommonUser(ChatId(RawChatId(12345L)), "John"))
+ assertEquals("[John](tg://user?id=12345)", entity.markdown)
+ assertEquals("John", entity.html)
+ }
+
+ @Test
+ fun customEmoji() {
+ val entity = RichTextCustomEmoji(CustomEmojiId("555"), "alt")
+ assertEquals("", entity.markdown)
+ assertEquals("alt", entity.html)
+ }
+
+ @Test
+ fun mathematicalExpression() {
+ val entity = RichTextMathematicalExpression("x^2")
+ assertEquals("\$x^2\$", entity.markdown)
+ assertEquals("x^2", entity.html)
+ }
+
+ @Test
+ fun url() {
+ val entity = RichTextUrl(RichTextPlain("link"), "https://t.me/")
+ assertEquals("[link](https://t.me/)", entity.markdown)
+ assertEquals("link", entity.html)
+ }
+
+ @Test
+ fun emailAddress() {
+ val entity = RichTextEmailAddress(RichTextPlain("mail"), "a@b.com")
+ assertEquals("[mail](mailto:a@b.com)", entity.markdown)
+ assertEquals("mail", entity.html)
+ }
+
+ @Test
+ fun phoneNumber() {
+ val entity = RichTextPhoneNumber(RichTextPlain("call"), "+123")
+ assertEquals("[call](tel:+123)", entity.markdown)
+ assertEquals("call", entity.html)
+ }
+
+ @Test
+ fun bankCardNumber() {
+ val entity = RichTextBankCardNumber(RichTextPlain("4242 4242 4242 4242"), "4242424242424242")
+ assertEquals("4242 4242 4242 4242", entity.markdown)
+ assertEquals("4242 4242 4242 4242", entity.html)
+ }
+
+ @Test
+ fun mention() {
+ val entity = RichTextMention(RichTextPlain("@user"), "user")
+ assertEquals("@user", entity.markdown)
+ assertEquals("@user", entity.html)
+ }
+
+ @Test
+ fun hashtag() {
+ val entity = RichTextHashtag(RichTextPlain("#tag"), "tag")
+ assertEquals("\\#tag", entity.markdown)
+ assertEquals("#tag", entity.html)
+ }
+
+ @Test
+ fun cashtag() {
+ val entity = RichTextCashtag(RichTextPlain("\$USD"), "USD")
+ assertEquals("\\\$USD", entity.markdown)
+ assertEquals("\$USD", entity.html)
+ }
+
+ @Test
+ fun botCommand() {
+ val entity = RichTextBotCommand(RichTextPlain("/start"), "start")
+ assertEquals("/start", entity.markdown)
+ assertEquals("/start", entity.html)
+ }
+
+ @Test
+ fun anchor() {
+ val entity = RichTextAnchor("top")
+ assertEquals("", entity.markdown)
+ assertEquals("", entity.html)
+ }
+
+ @Test
+ fun anchorLink() {
+ val entity = RichTextAnchorLink(RichTextPlain("go"), "top")
+ assertEquals("[go](#top)", entity.markdown)
+ assertEquals("go", entity.html)
+ }
+
+ @Test
+ fun reference() {
+ val entity = RichTextReference(RichTextPlain("ref"), "note1")
+ assertEquals("ref", entity.markdown)
+ assertEquals("ref", entity.html)
+ }
+
+ @Test
+ fun referenceLink() {
+ val entity = RichTextReferenceLink(RichTextPlain("see"), "note1")
+ assertEquals("[see](#note1)", entity.markdown)
+ assertEquals("see", entity.html)
+ }
+
+ @Test
+ fun nestedGroupRecursesIntoInnerEntities() {
+ val entity = RichTextBold(
+ RichTextGroup(
+ listOf(
+ RichTextPlain("a "),
+ RichTextItalic(RichTextPlain("b"))
+ )
+ )
+ )
+ assertEquals("**a *b***", entity.markdown)
+ assertEquals("a b", entity.html)
+ }
+
+ @Test
+ fun plainMarkdownEscapesSpecialCharacters() {
+ assertEquals("a\\*b\\_c", RichTextPlain("a*b_c").markdown)
+ assertEquals("\\[x\\]", RichTextPlain("[x]").markdown)
+ }
+
+ @Test
+ fun plainHtmlEscapesAngleBrackets() {
+ assertEquals("a<b", RichTextPlain("a