From 6b4999095e8ce7739cb6324a717a1b498bb4eb1b Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 30 Jun 2026 17:29:47 +0600 Subject: [PATCH] Add rich text / rich block DSL builder Add a type-safe Kotlin DSL for building rich messages: * buildRichText { } - RichTextBuilder with plain() plus a function for every RichTextEntity (String and nested RichTextBuilder overloads where text-bearing); * buildRichBlocks { } / buildRichTextInfo { } - RichBlocksBuilder with the text-bearing and container blocks (paragraph, heading, list, blockQuotation, details, ...), nesting RichText or further blocks per block kind; * RichBlockListBuilder for list items. Container blocks expose nested block/text builders; file/cell-heavy blocks (media, table, collage, slideshow, map) are appended via add() / unary plus. A @DslMarker (RichTextDsl) keeps the nested scopes from leaking receivers. Co-Authored-By: Claude Opus 4.8 --- tgbotapi.core/api/tgbotapi.core.api | 102 +++++++++ .../inmo/tgbotapi/types/rich/RichTextDsl.kt | 208 ++++++++++++++++++ .../tgbotapi/types/rich/RichTextDslTest.kt | 77 +++++++ 3 files changed, 387 insertions(+) create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDsl.kt create mode 100644 tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDslTest.kt diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index 38269f8a67..95e09872f0 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -34795,6 +34795,14 @@ public final class dev/inmo/tgbotapi/types/rich/RichBlockList$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class dev/inmo/tgbotapi/types/rich/RichBlockListBuilder { + public fun ()V + public final fun build ()Ljava/util/List; + public final fun item (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun item (Ljava/lang/String;Ljava/lang/String;)V + public static synthetic fun item$default (Ldev/inmo/tgbotapi/types/rich/RichBlockListBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V +} + public final class dev/inmo/tgbotapi/types/rich/RichBlockListItem { public static final field Companion Ldev/inmo/tgbotapi/types/rich/RichBlockListItem$Companion; public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;)V @@ -35263,6 +35271,35 @@ public final class dev/inmo/tgbotapi/types/rich/RichBlockVoiceNote$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class dev/inmo/tgbotapi/types/rich/RichBlocksBuilder { + public fun ()V + public final fun add (Ldev/inmo/tgbotapi/types/rich/RichBlock;)V + public final fun anchor (Ljava/lang/String;)V + public final fun blockQuotation (Ldev/inmo/tgbotapi/types/rich/RichText;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun blockQuotation$default (Ldev/inmo/tgbotapi/types/rich/RichBlocksBuilder;Ldev/inmo/tgbotapi/types/rich/RichText;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun build ()Ljava/util/List; + public final fun details (Ldev/inmo/tgbotapi/types/rich/RichText;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)V + public final fun details (Ljava/lang/String;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun details$default (Ldev/inmo/tgbotapi/types/rich/RichBlocksBuilder;Ldev/inmo/tgbotapi/types/rich/RichText;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun details$default (Ldev/inmo/tgbotapi/types/rich/RichBlocksBuilder;Ljava/lang/String;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun divider ()V + public final fun footer (Ljava/lang/String;)V + public final fun footer (Lkotlin/jvm/functions/Function1;)V + public final fun heading (ILjava/lang/String;)V + public final fun heading (ILkotlin/jvm/functions/Function1;)V + public final fun list (Lkotlin/jvm/functions/Function1;)V + public final fun mathematicalExpression (Ljava/lang/String;)V + public final fun paragraph (Ljava/lang/String;)V + public final fun paragraph (Lkotlin/jvm/functions/Function1;)V + public final fun preformatted (Ljava/lang/String;Ljava/lang/String;)V + public static synthetic fun preformatted$default (Ldev/inmo/tgbotapi/types/rich/RichBlocksBuilder;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V + public final fun pullQuotation (Ldev/inmo/tgbotapi/types/rich/RichText;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun pullQuotation$default (Ldev/inmo/tgbotapi/types/rich/RichBlocksBuilder;Ldev/inmo/tgbotapi/types/rich/RichText;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun thinking (Ljava/lang/String;)V + public final fun thinking (Lkotlin/jvm/functions/Function1;)V + public final fun unaryPlus (Ldev/inmo/tgbotapi/types/rich/RichBlock;)V +} + public abstract interface class dev/inmo/tgbotapi/types/rich/RichText { public static final field Companion Ldev/inmo/tgbotapi/types/rich/RichText$Companion; } @@ -35432,6 +35469,61 @@ public final class dev/inmo/tgbotapi/types/rich/RichTextBotCommand$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class dev/inmo/tgbotapi/types/rich/RichTextBuilder { + public fun ()V + public final fun add (Ldev/inmo/tgbotapi/types/rich/RichText;)V + public final fun anchor (Ljava/lang/String;)V + public final fun anchorLink (Ljava/lang/String;Ljava/lang/String;)V + public final fun anchorLink (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun bankCard (Ljava/lang/String;Ljava/lang/String;)V + public final fun bankCard (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun bold (Ljava/lang/String;)V + public final fun bold (Lkotlin/jvm/functions/Function1;)V + public final fun botCommand (Ljava/lang/String;Ljava/lang/String;)V + public final fun botCommand (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun build ()Ldev/inmo/tgbotapi/types/rich/RichText; + public final fun cashtag (Ljava/lang/String;Ljava/lang/String;)V + public final fun cashtag (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun code (Ljava/lang/String;)V + public final fun code (Lkotlin/jvm/functions/Function1;)V + public final fun customEmoji-R1fjqgo (Ljava/lang/String;Ljava/lang/String;)V + public final fun dateTime (JLjava/lang/String;Ljava/lang/String;)V + public final fun dateTime (JLjava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun email (Ljava/lang/String;Ljava/lang/String;)V + public final fun email (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun hashtag (Ljava/lang/String;Ljava/lang/String;)V + public final fun hashtag (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun italic (Ljava/lang/String;)V + public final fun italic (Lkotlin/jvm/functions/Function1;)V + public final fun marked (Ljava/lang/String;)V + public final fun marked (Lkotlin/jvm/functions/Function1;)V + public final fun mathematicalExpression (Ljava/lang/String;)V + public final fun mention (Ljava/lang/String;Ljava/lang/String;)V + public final fun mention (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun phone (Ljava/lang/String;Ljava/lang/String;)V + public final fun phone (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun plain (Ljava/lang/String;)V + public final fun reference (Ljava/lang/String;Ljava/lang/String;)V + public final fun reference (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun referenceLink (Ljava/lang/String;Ljava/lang/String;)V + public final fun referenceLink (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public final fun spoiler (Ljava/lang/String;)V + public final fun spoiler (Lkotlin/jvm/functions/Function1;)V + public final fun strikethrough (Ljava/lang/String;)V + public final fun strikethrough (Lkotlin/jvm/functions/Function1;)V + public final fun subscript (Ljava/lang/String;)V + public final fun subscript (Lkotlin/jvm/functions/Function1;)V + public final fun superscript (Ljava/lang/String;)V + public final fun superscript (Lkotlin/jvm/functions/Function1;)V + public final fun textMention (Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;)V + public final fun textMention (Ldev/inmo/tgbotapi/types/chat/User;Lkotlin/jvm/functions/Function1;)V + public final fun unaryPlus (Ldev/inmo/tgbotapi/types/rich/RichText;)V + public final fun underline (Ljava/lang/String;)V + public final fun underline (Lkotlin/jvm/functions/Function1;)V + public final fun url (Ljava/lang/String;Ljava/lang/String;)V + public final fun url (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V +} + public final class dev/inmo/tgbotapi/types/rich/RichTextCashtag : dev/inmo/tgbotapi/types/rich/RichTextEntity { public static final field Companion Ldev/inmo/tgbotapi/types/rich/RichTextCashtag$Companion; public static final field TYPE Ljava/lang/String; @@ -35564,6 +35656,16 @@ public final class dev/inmo/tgbotapi/types/rich/RichTextDateTime$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface annotation class dev/inmo/tgbotapi/types/rich/RichTextDsl : java/lang/annotation/Annotation { +} + +public final class dev/inmo/tgbotapi/types/rich/RichTextDslKt { + public static final fun buildRichBlocks (Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun buildRichText (Lkotlin/jvm/functions/Function1;)Ldev/inmo/tgbotapi/types/rich/RichText; + public static final fun buildRichTextInfo (Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)Ldev/inmo/tgbotapi/types/rich/RichTextInfo; + public static synthetic fun buildRichTextInfo$default (Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/rich/RichTextInfo; +} + public final class dev/inmo/tgbotapi/types/rich/RichTextEmailAddress : dev/inmo/tgbotapi/types/rich/RichTextEntity { public static final field Companion Ldev/inmo/tgbotapi/types/rich/RichTextEmailAddress$Companion; public static final field TYPE Ljava/lang/String; diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDsl.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDsl.kt new file mode 100644 index 0000000000..88805f81f7 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDsl.kt @@ -0,0 +1,208 @@ +package dev.inmo.tgbotapi.types.rich + +import dev.inmo.tgbotapi.types.CustomEmojiId +import dev.inmo.tgbotapi.types.chat.User + +/** + * [DslMarker] for the rich message builders, so the inner [RichTextBuilder], [RichBlocksBuilder] and + * [RichBlockListBuilder] scopes do not leak their receivers into each other. + */ +@DslMarker +annotation class RichTextDsl + +/** + * Builder of a single [RichText]. Each call appends a part; [build] returns a [RichTextPlain]/[RichTextEntity] when there + * is exactly one part, a [RichTextGroup] otherwise. + */ +@RichTextDsl +class RichTextBuilder { + private val parts = mutableListOf() + + /** Appends an already built [RichText]. */ + fun add(richText: RichText) { + parts.add(richText) + } + + /** Appends an already built [RichText]. */ + operator fun RichText.unaryPlus() = add(this) + + /** Plain, non-formatted text. */ + fun plain(text: String) = add(RichTextPlain(text)) + + fun bold(text: String) = add(RichTextBold(RichTextPlain(text))) + fun bold(block: RichTextBuilder.() -> Unit) = add(RichTextBold(buildRichText(block))) + + fun italic(text: String) = add(RichTextItalic(RichTextPlain(text))) + fun italic(block: RichTextBuilder.() -> Unit) = add(RichTextItalic(buildRichText(block))) + + fun underline(text: String) = add(RichTextUnderline(RichTextPlain(text))) + fun underline(block: RichTextBuilder.() -> Unit) = add(RichTextUnderline(buildRichText(block))) + + fun strikethrough(text: String) = add(RichTextStrikethrough(RichTextPlain(text))) + fun strikethrough(block: RichTextBuilder.() -> Unit) = add(RichTextStrikethrough(buildRichText(block))) + + fun spoiler(text: String) = add(RichTextSpoiler(RichTextPlain(text))) + fun spoiler(block: RichTextBuilder.() -> Unit) = add(RichTextSpoiler(buildRichText(block))) + + fun subscript(text: String) = add(RichTextSubscript(RichTextPlain(text))) + fun subscript(block: RichTextBuilder.() -> Unit) = add(RichTextSubscript(buildRichText(block))) + + fun superscript(text: String) = add(RichTextSuperscript(RichTextPlain(text))) + fun superscript(block: RichTextBuilder.() -> Unit) = add(RichTextSuperscript(buildRichText(block))) + + fun marked(text: String) = add(RichTextMarked(RichTextPlain(text))) + fun marked(block: RichTextBuilder.() -> Unit) = add(RichTextMarked(buildRichText(block))) + + fun code(text: String) = add(RichTextCode(RichTextPlain(text))) + fun code(block: RichTextBuilder.() -> Unit) = add(RichTextCode(buildRichText(block))) + + fun dateTime(unixTime: Long, dateTimeFormat: String, text: String) = + add(RichTextDateTime(RichTextPlain(text), unixTime, dateTimeFormat)) + fun dateTime(unixTime: Long, dateTimeFormat: String, block: RichTextBuilder.() -> Unit) = + add(RichTextDateTime(buildRichText(block), unixTime, dateTimeFormat)) + + fun textMention(user: User, text: String) = add(RichTextTextMention(RichTextPlain(text), user)) + fun textMention(user: User, block: RichTextBuilder.() -> Unit) = add(RichTextTextMention(buildRichText(block), user)) + + fun customEmoji(customEmojiId: CustomEmojiId, alternativeText: String) = + add(RichTextCustomEmoji(customEmojiId, alternativeText)) + + fun mathematicalExpression(expression: String) = add(RichTextMathematicalExpression(expression)) + + fun url(url: String, text: String) = add(RichTextUrl(RichTextPlain(text), url)) + fun url(url: String, block: RichTextBuilder.() -> Unit) = add(RichTextUrl(buildRichText(block), url)) + + fun email(emailAddress: String, text: String) = add(RichTextEmailAddress(RichTextPlain(text), emailAddress)) + fun email(emailAddress: String, block: RichTextBuilder.() -> Unit) = + add(RichTextEmailAddress(buildRichText(block), emailAddress)) + + fun phone(phoneNumber: String, text: String) = add(RichTextPhoneNumber(RichTextPlain(text), phoneNumber)) + fun phone(phoneNumber: String, block: RichTextBuilder.() -> Unit) = + add(RichTextPhoneNumber(buildRichText(block), phoneNumber)) + + fun bankCard(bankCardNumber: String, text: String) = add(RichTextBankCardNumber(RichTextPlain(text), bankCardNumber)) + fun bankCard(bankCardNumber: String, block: RichTextBuilder.() -> Unit) = + add(RichTextBankCardNumber(buildRichText(block), bankCardNumber)) + + fun mention(username: String, text: String) = add(RichTextMention(RichTextPlain(text), username)) + fun mention(username: String, block: RichTextBuilder.() -> Unit) = add(RichTextMention(buildRichText(block), username)) + + fun hashtag(hashtag: String, text: String) = add(RichTextHashtag(RichTextPlain(text), hashtag)) + fun hashtag(hashtag: String, block: RichTextBuilder.() -> Unit) = add(RichTextHashtag(buildRichText(block), hashtag)) + + fun cashtag(cashtag: String, text: String) = add(RichTextCashtag(RichTextPlain(text), cashtag)) + fun cashtag(cashtag: String, block: RichTextBuilder.() -> Unit) = add(RichTextCashtag(buildRichText(block), cashtag)) + + fun botCommand(botCommand: String, text: String) = add(RichTextBotCommand(RichTextPlain(text), botCommand)) + fun botCommand(botCommand: String, block: RichTextBuilder.() -> Unit) = + add(RichTextBotCommand(buildRichText(block), botCommand)) + + fun anchor(name: String) = add(RichTextAnchor(name)) + + fun anchorLink(anchorName: String, text: String) = add(RichTextAnchorLink(RichTextPlain(text), anchorName)) + fun anchorLink(anchorName: String, block: RichTextBuilder.() -> Unit) = + add(RichTextAnchorLink(buildRichText(block), anchorName)) + + fun reference(name: String, text: String) = add(RichTextReference(RichTextPlain(text), name)) + fun reference(name: String, block: RichTextBuilder.() -> Unit) = add(RichTextReference(buildRichText(block), name)) + + fun referenceLink(referenceName: String, text: String) = + add(RichTextReferenceLink(RichTextPlain(text), referenceName)) + fun referenceLink(referenceName: String, block: RichTextBuilder.() -> Unit) = + add(RichTextReferenceLink(buildRichText(block), referenceName)) + + fun build(): RichText = when (parts.size) { + 0 -> RichTextGroup(emptyList()) + 1 -> parts.single() + else -> RichTextGroup(parts.toList()) + } +} + +/** + * Builder of [RichBlockListItem]s used inside [RichBlocksBuilder.list]. + */ +@RichTextDsl +class RichBlockListBuilder { + private val items = mutableListOf() + + fun item( + label: String, + hasCheckbox: Boolean? = null, + isChecked: Boolean? = null, + value: Int? = null, + labelType: String? = null, + block: RichBlocksBuilder.() -> Unit + ) { + items.add(RichBlockListItem(label, buildRichBlocks(block), hasCheckbox, isChecked, value, labelType)) + } + + fun item(label: String, text: String) { + items.add(RichBlockListItem(label, listOf(RichBlockParagraph(RichTextPlain(text))))) + } + + fun build(): List = items.toList() +} + +/** + * Builder of a [List] of [RichBlock]s - the root of the rich message DSL. Text-bearing and container blocks have their + * own DSL functions; file/cell-heavy blocks (media, collage, slideshow, table, map) can be appended with [add] / unary + * plus. + */ +@RichTextDsl +class RichBlocksBuilder { + private val blocks = mutableListOf() + + /** Appends an already built [RichBlock]. */ + fun add(block: RichBlock) { + blocks.add(block) + } + + /** Appends an already built [RichBlock]. */ + operator fun RichBlock.unaryPlus() = add(this) + + fun paragraph(text: String) = add(RichBlockParagraph(RichTextPlain(text))) + fun paragraph(block: RichTextBuilder.() -> Unit) = add(RichBlockParagraph(buildRichText(block))) + + fun heading(size: Int, text: String) = add(RichBlockSectionHeading(RichTextPlain(text), size)) + fun heading(size: Int, block: RichTextBuilder.() -> Unit) = add(RichBlockSectionHeading(buildRichText(block), size)) + + fun preformatted(text: String, language: String? = null) = add(RichBlockPreformatted(RichTextPlain(text), language)) + + fun footer(text: String) = add(RichBlockFooter(RichTextPlain(text))) + fun footer(block: RichTextBuilder.() -> Unit) = add(RichBlockFooter(buildRichText(block))) + + fun divider() = add(RichBlockDivider()) + + fun mathematicalExpression(expression: String) = add(RichBlockMathematicalExpression(expression)) + + fun anchor(name: String) = add(RichBlockAnchor(name)) + + fun thinking(text: String) = add(RichBlockThinking(RichTextPlain(text))) + fun thinking(block: RichTextBuilder.() -> Unit) = add(RichBlockThinking(buildRichText(block))) + + fun list(block: RichBlockListBuilder.() -> Unit) = add(RichBlockList(RichBlockListBuilder().apply(block).build())) + + fun blockQuotation(credit: RichText? = null, block: RichBlocksBuilder.() -> Unit) = + add(RichBlockBlockQuotation(buildRichBlocks(block), credit)) + + fun pullQuotation(credit: RichText? = null, block: RichTextBuilder.() -> Unit) = + add(RichBlockPullQuotation(buildRichText(block), credit)) + + fun details(summary: RichText, isOpen: Boolean? = null, block: RichBlocksBuilder.() -> Unit) = + add(RichBlockDetails(summary, buildRichBlocks(block), isOpen)) + + fun details(summary: String, isOpen: Boolean? = null, block: RichBlocksBuilder.() -> Unit) = + details(RichTextPlain(summary), isOpen, block) + + fun build(): List = blocks.toList() +} + +/** Builds a [RichText] using the [RichTextBuilder] DSL. */ +fun buildRichText(block: RichTextBuilder.() -> Unit): RichText = RichTextBuilder().apply(block).build() + +/** Builds a [List] of [RichBlock]s using the [RichBlocksBuilder] DSL. */ +fun buildRichBlocks(block: RichBlocksBuilder.() -> Unit): List = RichBlocksBuilder().apply(block).build() + +/** Builds a [RichTextInfo] using the [RichBlocksBuilder] DSL. */ +fun buildRichTextInfo(isRtl: Boolean? = null, block: RichBlocksBuilder.() -> Unit): RichTextInfo = + RichTextInfo(buildRichBlocks(block), isRtl) diff --git a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDslTest.kt b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDslTest.kt new file mode 100644 index 0000000000..c4f3ca274c --- /dev/null +++ b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/rich/RichTextDslTest.kt @@ -0,0 +1,77 @@ +package dev.inmo.tgbotapi.types.rich + +import kotlin.test.Test +import kotlin.test.assertEquals + +class RichTextDslTest { + @Test + fun buildsRichTextGroup() { + val richText = buildRichText { + plain("a ") + bold("b") + italic { + plain("c") + bold("d") + } + } + assertEquals( + RichTextGroup( + listOf( + RichTextPlain("a "), + RichTextBold(RichTextPlain("b")), + RichTextItalic(RichTextGroup(listOf(RichTextPlain("c"), RichTextBold(RichTextPlain("d"))))) + ) + ), + richText + ) + } + + @Test + fun singlePartUnwraps() { + assertEquals(RichTextBold(RichTextPlain("x")), buildRichText { bold("x") }) + } + + @Test + fun rendersMarkdown() { + assertEquals("a **b**", buildRichText { plain("a "); bold("b") }.markdown) + } + + @Test + fun buildsBlocks() { + val blocks = buildRichBlocks { + heading(1, "Title") + paragraph { + plain("Hello ") + bold("world") + } + divider() + list { + item("1", "first") + item("2", labelType = "1") { paragraph("second") } + } + blockQuotation { + paragraph("quoted") + } + } + assertEquals(5, blocks.size) + assertEquals(RichBlockSectionHeading(RichTextPlain("Title"), 1), blocks[0]) + assertEquals( + RichBlockParagraph(RichTextGroup(listOf(RichTextPlain("Hello "), RichTextBold(RichTextPlain("world"))))), + blocks[1] + ) + assertEquals(RichBlockDivider(), blocks[2]) + val list = blocks[3] as RichBlockList + assertEquals(2, list.items.size) + assertEquals(RichBlockListItem("1", listOf(RichBlockParagraph(RichTextPlain("first")))), list.items[0]) + assertEquals(RichBlockBlockQuotation(listOf(RichBlockParagraph(RichTextPlain("quoted")))), blocks[4]) + } + + @Test + fun buildsRichTextInfo() { + val info = buildRichTextInfo(isRtl = true) { + paragraph("p") + } + assertEquals(RichTextInfo(listOf(RichBlockParagraph(RichTextPlain("p"))), true), info) + assertEquals("p", info.markdown) + } +}