Skip to content

Commit e5f142f

Browse files
zimegClaude
andcommitted
Allow mrkdwn descriptions on OptionObject (fixes #1471)
`OptionObject.description` was hard-typed to `PlainTextObject`, so a `mrkdwn` description on a radio-button or checkbox option was coerced to plain_text on deserialize, losing the markdown. The option-object docs state that radio buttons and checkboxes can use mrkdwn text objects for the description. Widen the field to `TextObject` (the same type already used for `OptionObject.text`), which routes deserialization through the existing polymorphic text-object factory so `mrkdwn` round-trips as a `MarkdownTextObject` and `plain_text` still returns a `PlainTextObject`. The Kotlin DSL gains a `markdownDescription(...)` overload alongside the existing plain_text `description(...)`. Source-compatible: all call sites pass plain_text and the getter's declared type only broadens. Ref: https://docs.slack.dev/reference/block-kit/composition-objects/option-object Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
1 parent dbe498c commit e5f142f

4 files changed

Lines changed: 144 additions & 7 deletions

File tree

‎slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/OptionObjectBuilder.kt‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.slack.api.model.kotlin_extension.block.composition
22

3+
import com.slack.api.model.block.composition.MarkdownTextObject
34
import com.slack.api.model.block.composition.OptionObject
45
import com.slack.api.model.block.composition.PlainTextObject
6+
import com.slack.api.model.block.composition.TextObject
57
import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder
68
import com.slack.api.model.kotlin_extension.block.Builder
79
import com.slack.api.model.kotlin_extension.block.composition.container.SingleTextObjectContainer
@@ -14,7 +16,7 @@ class OptionObjectBuilder private constructor(
1416
) : Builder<OptionObject>, TextObjectDsl by textContainer {
1517
private var value: String? = null
1618
private var url: String? = null
17-
private var description: PlainTextObject? = null
19+
private var description: TextObject? = null
1820

1921
constructor() : this(SingleTextObjectContainer())
2022

@@ -40,15 +42,25 @@ class OptionObjectBuilder private constructor(
4042
}
4143

4244
/**
43-
* a line of descriptive text shown below the text field beside the radio button. Maximum length for the text
44-
* object within this field is 75 characters.
45+
* A plain_text line of descriptive text shown below the text field beside the radio button. Maximum length for the
46+
* text object within this field is 75 characters.
4547
*
4648
* @see <a href="https://docs.slack.dev/reference/block-kit/composition-objects/option-object">Option object documentation</a>
4749
*/
4850
fun description(text: String, emoji: Boolean? = null) {
4951
description = PlainTextObject(text, emoji)
5052
}
5153

54+
/**
55+
* A mrkdwn line of descriptive text shown below the text field beside the radio button. Only radio buttons and
56+
* checkboxes can use mrkdwn descriptions. Maximum length for the text object within this field is 75 characters.
57+
*
58+
* @see <a href="https://docs.slack.dev/reference/block-kit/composition-objects/option-object">Option object documentation</a>
59+
*/
60+
fun markdownDescription(text: String, verbatim: Boolean? = null) {
61+
description = MarkdownTextObject(text, verbatim)
62+
}
63+
5264
override fun build(): OptionObject {
5365
return OptionObject.builder()
5466
.description(description)

‎slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/ActionsBlockTest.kt‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,4 +504,69 @@ class ActionsBlockTest {
504504
val actual = gson.toJsonTree(blocks)
505505
assertEquals(expected, actual, "\n$expected\n$actual")
506506
}
507+
508+
@Test
509+
fun `checkboxes with mrkdwn option description`() {
510+
val gson = GsonFactory.createSnakeCase()
511+
val blocks = withBlocks {
512+
actions {
513+
checkboxes {
514+
options {
515+
option {
516+
plainText("Checkbox 1")
517+
markdownDescription("*bold* description")
518+
value("mrkdwn-desc")
519+
}
520+
option {
521+
plainText("Checkbox 2")
522+
description("plain description")
523+
value("plain-desc")
524+
}
525+
}
526+
}
527+
}
528+
}
529+
val original = """
530+
{
531+
"blocks": [
532+
{
533+
"type": "actions",
534+
"elements": [
535+
{
536+
"type": "checkboxes",
537+
"options": [
538+
{
539+
"text": {
540+
"type": "plain_text",
541+
"text": "Checkbox 1"
542+
},
543+
"value": "mrkdwn-desc",
544+
"description": {
545+
"type": "mrkdwn",
546+
"text": "*bold* description"
547+
}
548+
},
549+
{
550+
"text": {
551+
"type": "plain_text",
552+
"text": "Checkbox 2"
553+
},
554+
"value": "plain-desc",
555+
"description": {
556+
"type": "plain_text",
557+
"text": "plain description"
558+
}
559+
}
560+
]
561+
}
562+
]
563+
}
564+
]
565+
}
566+
""".trimIndent()
567+
val json = gson.fromJson(original, JsonElement::class.java)
568+
val expected = json.asJsonObject["blocks"]
569+
val actual = gson.toJsonTree(blocks)
570+
assertEquals(expected, actual, "\n$expected\n$actual")
571+
}
507572
}

‎slack-api-model/src/main/java/com/slack/api/model/block/composition/OptionObject.java‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ public class OptionObject {
2929
private String value;
3030

3131
/**
32-
* A plain_text only text object that defines a line of descriptive text shown
33-
* below the text field beside the radio button.
34-
* Maximum length for the text object within this field is 75 characters.
32+
* A plain_text text object that defines a line of descriptive text shown below
33+
* the text field beside a single selectable item in a select menu, multi-select
34+
* menu, checkbox group, radio button group, or overflow menu. Checkbox group and
35+
* radio button group items can also use mrkdwn formatting.
36+
* Maximum length for the text within this field is 75 characters.
3537
*/
36-
private PlainTextObject description;
38+
private TextObject description;
3739

3840
/**
3941
* A URL to load in the user's browser when the option is clicked.

‎slack-api-model/src/test/java/test_locally/api/model/block/BlockKitTest.java‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import com.slack.api.model.Message;
66
import com.slack.api.model.block.*;
77
import com.slack.api.model.block.composition.ConfirmationDialogObject;
8+
import com.slack.api.model.block.composition.MarkdownTextObject;
9+
import com.slack.api.model.block.composition.OptionObject;
10+
import com.slack.api.model.block.composition.PlainTextObject;
811
import com.slack.api.model.block.element.*;
912
import com.slack.api.model.view.View;
1013
import org.junit.Test;
@@ -1128,6 +1131,61 @@ public void parseCheckboxes() {
11281131
assertThat(checkboxes2.getActionId(), is("section-action-id"));
11291132
}
11301133

1134+
@Test
1135+
public void parseCheckboxesWithMrkdwnOptionDescription() {
1136+
String json = "{" +
1137+
" \"blocks\": [\n" +
1138+
" {\n" +
1139+
" \"type\": \"actions\",\n" +
1140+
" \"elements\": [\n" +
1141+
" {\n" +
1142+
" \"type\": \"checkboxes\",\n" +
1143+
" \"action_id\": \"this_is_an_action_id\",\n" +
1144+
" \"options\": [\n" +
1145+
" {\n" +
1146+
" \"value\": \"A1\",\n" +
1147+
" \"text\": {\n" +
1148+
" \"type\": \"plain_text\",\n" +
1149+
" \"text\": \"Checkbox 1\"\n" +
1150+
" },\n" +
1151+
" \"description\": {\n" +
1152+
" \"type\": \"mrkdwn\",\n" +
1153+
" \"text\": \"*bold* description\"\n" +
1154+
" }\n" +
1155+
" },\n" +
1156+
" {\n" +
1157+
" \"value\": \"A2\",\n" +
1158+
" \"text\": {\n" +
1159+
" \"type\": \"plain_text\",\n" +
1160+
" \"text\": \"Checkbox 2\"\n" +
1161+
" },\n" +
1162+
" \"description\": {\n" +
1163+
" \"type\": \"plain_text\",\n" +
1164+
" \"text\": \"plain description\"\n" +
1165+
" }\n" +
1166+
" }\n" +
1167+
" ]\n" +
1168+
" }\n" +
1169+
" ]\n" +
1170+
" }\n" +
1171+
" ]\n" +
1172+
"}";
1173+
Message message = GsonFactory.createSnakeCase().fromJson(json, Message.class);
1174+
assertThat(message, is(notNullValue()));
1175+
ActionsBlock block = (ActionsBlock) message.getBlocks().get(0);
1176+
CheckboxesElement checkboxes = (CheckboxesElement) block.getElements().get(0);
1177+
1178+
OptionObject mrkdwnOption = checkboxes.getOptions().get(0);
1179+
assertThat(mrkdwnOption.getDescription(), instanceOf(MarkdownTextObject.class));
1180+
assertThat(mrkdwnOption.getDescription().getType(), is("mrkdwn"));
1181+
assertThat(((MarkdownTextObject) mrkdwnOption.getDescription()).getText(), is("*bold* description"));
1182+
1183+
OptionObject plainOption = checkboxes.getOptions().get(1);
1184+
assertThat(plainOption.getDescription(), instanceOf(PlainTextObject.class));
1185+
assertThat(plainOption.getDescription().getType(), is("plain_text"));
1186+
assertThat(((PlainTextObject) plainOption.getDescription()).getText(), is("plain description"));
1187+
}
1188+
11311189
@Test
11321190
public void confirm_style() {
11331191
String json = "{\n" +

0 commit comments

Comments
 (0)