Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.slack.api.model.kotlin_extension.block

import com.slack.api.model.block.ContainerBlock
import com.slack.api.model.block.LayoutBlock
import com.slack.api.model.block.RichTextBlock
import com.slack.api.model.block.composition.PlainTextObject
import com.slack.api.model.block.composition.TextObject
import com.slack.api.model.block.element.ImageElement
import com.slack.api.model.kotlin_extension.block.container.MultiLayoutBlockContainer

@BlockLayoutBuilder
class ContainerBlockBuilder : Builder<ContainerBlock> {
private var blockId: String? = null
private var title: PlainTextObject? = null
private var richTextTitle: RichTextBlock? = null
private var subtitle: TextObject? = null
private var childBlocks: List<LayoutBlock>? = null
private var width: String? = null
private var icon: ImageElement? = null
private var isCollapsible: Boolean? = null
private var defaultCollapsed: Boolean? = null
private var hasHeaderDivider: Boolean? = null

fun blockId(id: String) {
blockId = id
}

fun title(text: String, emoji: Boolean? = null) {
title = PlainTextObject(text, emoji)
}

fun title(textObject: PlainTextObject) {
title = textObject
}

fun richTextTitle(richText: RichTextBlock) {
richTextTitle = richText
}

fun subtitle(text: String, emoji: Boolean? = null) {
subtitle = PlainTextObject(text, emoji)
}

fun subtitle(textObject: TextObject) {
subtitle = textObject
}

fun childBlocks(builder: MultiLayoutBlockContainer.() -> Unit) {
childBlocks = MultiLayoutBlockContainer().apply(builder).underlying
}

fun width(width: String) {
this.width = width
}

fun icon(imageUrl: String, altText: String) {
icon = ImageElement.builder().imageUrl(imageUrl).altText(altText).build()
}

fun isCollapsible(collapsible: Boolean) {
isCollapsible = collapsible
}

fun defaultCollapsed(collapsed: Boolean) {
defaultCollapsed = collapsed
}

fun hasHeaderDivider(divider: Boolean) {
hasHeaderDivider = divider
}

override fun build(): ContainerBlock {
return ContainerBlock.builder()
.blockId(blockId)
.title(title)
.richTextTitle(richTextTitle)
.subtitle(subtitle)
.childBlocks(childBlocks ?: ArrayList())
.width(width)
.icon(icon)
.isCollapsible(isCollapsible)
.defaultCollapsed(defaultCollapsed)
.hasHeaderDivider(hasHeaderDivider)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ class MultiLayoutBlockContainer : LayoutBlockDsl {
override fun shareShortcut(builder: ShareShortcutBlockBuilder.() -> Unit) {
underlying += ShareShortcutBlockBuilder().apply(builder).build()
}

override fun container(builder: ContainerBlockBuilder.() -> Unit) {
underlying += ContainerBlockBuilder().apply(builder).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,11 @@ interface LayoutBlockDsl {
* @see <a href="https://tools.slack.dev/deno-slack-sdk/">Next generation platform</a>
*/
fun shareShortcut(builder: ShareShortcutBlockBuilder.() -> Unit)

/**
* A container block groups related blocks together into a visually distinct section.
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/container-block">Container documentation</a>
*/
fun container(builder: ContainerBlockBuilder.() -> Unit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package test_locally.block

import com.slack.api.model.block.ContainerBlock
import com.slack.api.model.block.DividerBlock
import com.slack.api.model.block.SectionBlock
import com.slack.api.model.block.composition.MarkdownTextObject
import com.slack.api.model.block.composition.PlainTextObject
import com.slack.api.model.kotlin_extension.block.withBlocks
import com.slack.api.util.json.GsonFactory
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue

class ContainerBlockTest {

@Test
fun constructBasicContainer() {
val gson = GsonFactory.createSnakeCase()
val blocks = withBlocks {
container {
blockId("container-1")
title("Container Title")
width("wide")
isCollapsible(true)
defaultCollapsed(false)
hasHeaderDivider(true)
icon("https://example.com/icon.png", "icon")
childBlocks {
divider()
}
}
}
assertEquals(1, blocks.size)
val container = blocks[0] as ContainerBlock
assertEquals("container", container.type)
assertEquals("container-1", container.blockId)
assertEquals("Container Title", container.title.text)
assertEquals("wide", container.width)
assertEquals(true, container.isCollapsible)
assertEquals(false, container.defaultCollapsed)
assertEquals(true, container.hasHeaderDivider)
assertNotNull(container.icon)
assertEquals(1, container.childBlocks.size)
assertTrue(container.childBlocks[0] is DividerBlock)

val json = gson.toJson(blocks)
assertTrue(json.contains("\"type\":\"container\""))
assertTrue(json.contains("\"block_id\":\"container-1\""))
}

@Test
fun constructContainerWithPlainTextObject() {
val titleObj = PlainTextObject.builder().text("Title with emoji").emoji(true).build()
val blocks = withBlocks {
container {
title(titleObj)
childBlocks {
divider()
}
}
}
val container = blocks[0] as ContainerBlock
assertEquals("Title with emoji", container.title.text)
}

@Test
fun constructContainerWithSubtitle() {
val blocks = withBlocks {
container {
title("Title")
subtitle("Plain subtitle")
childBlocks {
divider()
}
}
}
val container = blocks[0] as ContainerBlock
assertNotNull(container.subtitle)
assertEquals("Plain subtitle", container.subtitle.text)
}

@Test
fun constructContainerWithMarkdownSubtitle() {
val blocks = withBlocks {
container {
title("Title")
subtitle(MarkdownTextObject.builder().text("*Bold* subtitle").build())
childBlocks {
divider()
}
}
}
val container = blocks[0] as ContainerBlock
assertNotNull(container.subtitle)
assertEquals("mrkdwn", container.subtitle.type)
}

@Test
fun constructContainerWithMultipleChildBlocks() {
val blocks = withBlocks {
container {
title("Title")
childBlocks {
section {
markdownText("Section inside container")
}
divider()
}
}
}
val container = blocks[0] as ContainerBlock
assertEquals(2, container.childBlocks.size)
assertTrue(container.childBlocks[0] is SectionBlock)
assertTrue(container.childBlocks[1] is DividerBlock)
}

@Test
fun constructContainerWithoutOptionalFields() {
val blocks = withBlocks {
container {
title("Minimal")
childBlocks {
divider()
}
}
}
val container = blocks[0] as ContainerBlock
assertNull(container.blockId)
assertNull(container.width)
assertNull(container.isCollapsible)
assertNull(container.defaultCollapsed)
assertNull(container.hasHeaderDivider)
assertNull(container.icon)
assertNull(container.richTextTitle)
}

@Test
fun roundTripSerialization() {
val gson = GsonFactory.createSnakeCase()
val blocks = withBlocks {
container {
blockId("rt-1")
title("Round trip")
subtitle("Sub")
width("wide")
isCollapsible(true)
childBlocks {
divider()
}
}
}
val json = gson.toJson(blocks)
val reparsed = gson.fromJson(json, Array<ContainerBlock>::class.java)
assertEquals("rt-1", reparsed[0].blockId)
assertEquals("Round trip", reparsed[0].title.text)
assertEquals("wide", reparsed[0].width)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ public static CarouselBlock carousel(ModelConfigurator<CarouselBlock.CarouselBlo
return configurator.configure(CarouselBlock.builder()).build();
}

// ContainerBlock

public static ContainerBlock container(ModelConfigurator<ContainerBlock.ContainerBlockBuilder> configurator) {
return configurator.configure(ContainerBlock.builder()).build();
}

// TaskCardBlock

public static TaskCardBlock taskCard(ModelConfigurator<TaskCardBlock.TaskCardBlockBuilder> configurator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.slack.api.model.block;

import com.slack.api.model.block.composition.PlainTextObject;
import com.slack.api.model.block.composition.TextObject;
import com.slack.api.model.block.element.ImageElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

/**
* https://docs.slack.dev/reference/block-kit/blocks/container-block
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContainerBlock implements LayoutBlock {
public static final String TYPE = "container";
private final String type = TYPE;

private PlainTextObject title;

private RichTextBlock richTextTitle;

private TextObject subtitle;

@Builder.Default
private List<LayoutBlock> childBlocks = new ArrayList<>();

private String width;

private ImageElement icon;

private Boolean isCollapsible;

private Boolean defaultCollapsed;

private Boolean hasHeaderDivider;

private String blockId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ private Class<? extends LayoutBlock> getLayoutClassInstance(String typeName) {
return CardBlock.class;
case CarouselBlock.TYPE:
return CarouselBlock.class;
case ContainerBlock.TYPE:
return ContainerBlock.class;
case ContextActionsBlock.TYPE:
return ContextActionsBlock.class;
case ContextBlock.TYPE:
Expand Down
Loading
Loading