From f1b042f50753807256c8046cb88a1fd5cb4fadf1 Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Tue, 24 Dec 2024 16:45:07 +0100 Subject: [PATCH 1/3] feat: add rich text block, elements and parts --- src/Blocks/RichText.php | 35 +++ src/Collections/RichTextCollection.php | 51 +++++ src/Collections/RichTextSectionCollection.php | 51 +++++ src/Collections/RichTextSubCollection.php | 48 ++++ src/Collections/TextCollection.php | 50 +++++ src/Elements/RichTexts/Broadcast.php | 28 +++ src/Elements/RichTexts/Channel.php | 35 +++ src/Elements/RichTexts/Color.php | 29 +++ src/Elements/RichTexts/Date.php | 67 ++++++ src/Elements/RichTexts/Emoji.php | 40 ++++ src/Elements/RichTexts/Link.php | 68 ++++++ src/Elements/RichTexts/ListStyle.php | 16 ++ src/Elements/RichTexts/Range.php | 15 ++ src/Elements/RichTexts/RichTextElement.php | 11 + src/Elements/RichTexts/RichTextList.php | 78 +++++++ .../RichTexts/RichTextPreformatted.php | 39 ++++ src/Elements/RichTexts/RichTextQuote.php | 28 +++ src/Elements/RichTexts/RichTextSection.php | 30 +++ src/Elements/RichTexts/RichTextSubElement.php | 11 + src/Elements/RichTexts/Text.php | 46 ++++ src/Elements/RichTexts/Traits/HasBorder.php | 21 ++ .../RichTexts/Traits/HasMentionStyle.php | 21 ++ .../RichTexts/Traits/HasRichTextElements.php | 25 +++ src/Elements/RichTexts/User.php | 34 +++ src/Elements/RichTexts/Usergroup.php | 34 +++ src/Kit.php | 207 ++++++++++++++++-- src/Parts/MentionStyle.php | 67 ++++++ src/Parts/Style.php | 43 ++++ src/Parts/Traits/HasBold.php | 20 ++ src/Parts/Traits/HasItalic.php | 20 ++ src/Parts/Traits/HasStrike.php | 20 ++ src/Type.php | 36 ++- tests/Functional/BlockTest.php | 17 ++ .../Collections/RichTextCollectionTest.php | 37 ++++ .../RichTextSectionCollectionTest.php | 57 +++++ .../Collections/RichTextSubCollectionTest.php | 63 ++++++ .../Collections/TextCollectionTest.php | 45 ++++ .../Elements/RichTexts/BroadcastTest.php | 38 ++++ .../Elements/RichTexts/ChannelTest.php | 74 +++++++ .../Elements/RichTexts/ColorTest.php | 37 ++++ .../Elements/RichTexts/DateTest.php | 86 ++++++++ .../Elements/RichTexts/EmojiTest.php | 64 ++++++ .../Elements/RichTexts/LinkTest.php | 92 ++++++++ .../Elements/RichTexts/ListStyleTest.php | 22 ++ .../Elements/RichTexts/RangeTest.php | 24 ++ .../Elements/RichTexts/RichTextListTest.php | 63 ++++++ .../RichTexts/RichTextPreformattedTest.php | 78 +++++++ .../Elements/RichTexts/RichTextQuoteTest.php | 78 +++++++ .../RichTexts/RichTextSectionTest.php | 54 +++++ .../Elements/RichTexts/TextTest.php | 72 ++++++ .../Elements/RichTexts/UserTest.php | 74 +++++++ .../Elements/RichTexts/UsergroupTest.php | 74 +++++++ tests/Functional/Parts/MentionStyleTest.php | 71 ++++++ tests/Functional/Parts/StyleTest.php | 65 ++++++ .../assets/blocks/rich_text_broadcast.json | 14 ++ .../assets/blocks/rich_text_channel.json | 14 ++ .../assets/blocks/rich_text_color.json | 14 ++ .../assets/blocks/rich_text_date.json | 16 ++ .../assets/blocks/rich_text_emoji.json | 30 +++ .../assets/blocks/rich_text_link.json | 14 ++ .../assets/blocks/rich_text_list.json | 50 +++++ .../assets/blocks/rich_text_preformatted.json | 15 ++ .../assets/blocks/rich_text_quote.json | 24 ++ .../blocks/rich_text_section_basic.json | 14 ++ .../assets/blocks/rich_text_section_bold.json | 21 ++ .../blocks/rich_text_section_italic.json | 21 ++ .../rich_text_section_strikethrough.json | 21 ++ .../assets/blocks/rich_text_text.json | 21 ++ .../assets/blocks/rich_text_user.json | 14 ++ .../assets/blocks/rich_text_usergroup.json | 14 ++ 70 files changed, 2901 insertions(+), 25 deletions(-) create mode 100644 src/Blocks/RichText.php create mode 100644 src/Collections/RichTextCollection.php create mode 100644 src/Collections/RichTextSectionCollection.php create mode 100644 src/Collections/RichTextSubCollection.php create mode 100644 src/Collections/TextCollection.php create mode 100644 src/Elements/RichTexts/Broadcast.php create mode 100644 src/Elements/RichTexts/Channel.php create mode 100644 src/Elements/RichTexts/Color.php create mode 100644 src/Elements/RichTexts/Date.php create mode 100644 src/Elements/RichTexts/Emoji.php create mode 100644 src/Elements/RichTexts/Link.php create mode 100644 src/Elements/RichTexts/ListStyle.php create mode 100644 src/Elements/RichTexts/Range.php create mode 100644 src/Elements/RichTexts/RichTextElement.php create mode 100644 src/Elements/RichTexts/RichTextList.php create mode 100644 src/Elements/RichTexts/RichTextPreformatted.php create mode 100644 src/Elements/RichTexts/RichTextQuote.php create mode 100644 src/Elements/RichTexts/RichTextSection.php create mode 100644 src/Elements/RichTexts/RichTextSubElement.php create mode 100644 src/Elements/RichTexts/Text.php create mode 100644 src/Elements/RichTexts/Traits/HasBorder.php create mode 100644 src/Elements/RichTexts/Traits/HasMentionStyle.php create mode 100644 src/Elements/RichTexts/Traits/HasRichTextElements.php create mode 100644 src/Elements/RichTexts/User.php create mode 100644 src/Elements/RichTexts/Usergroup.php create mode 100644 src/Parts/MentionStyle.php create mode 100644 src/Parts/Style.php create mode 100644 src/Parts/Traits/HasBold.php create mode 100644 src/Parts/Traits/HasItalic.php create mode 100644 src/Parts/Traits/HasStrike.php create mode 100644 tests/Functional/Collections/RichTextCollectionTest.php create mode 100644 tests/Functional/Collections/RichTextSectionCollectionTest.php create mode 100644 tests/Functional/Collections/RichTextSubCollectionTest.php create mode 100644 tests/Functional/Collections/TextCollectionTest.php create mode 100644 tests/Functional/Elements/RichTexts/BroadcastTest.php create mode 100644 tests/Functional/Elements/RichTexts/ChannelTest.php create mode 100644 tests/Functional/Elements/RichTexts/ColorTest.php create mode 100644 tests/Functional/Elements/RichTexts/DateTest.php create mode 100644 tests/Functional/Elements/RichTexts/EmojiTest.php create mode 100644 tests/Functional/Elements/RichTexts/LinkTest.php create mode 100644 tests/Functional/Elements/RichTexts/ListStyleTest.php create mode 100644 tests/Functional/Elements/RichTexts/RangeTest.php create mode 100644 tests/Functional/Elements/RichTexts/RichTextListTest.php create mode 100644 tests/Functional/Elements/RichTexts/RichTextPreformattedTest.php create mode 100644 tests/Functional/Elements/RichTexts/RichTextQuoteTest.php create mode 100644 tests/Functional/Elements/RichTexts/RichTextSectionTest.php create mode 100644 tests/Functional/Elements/RichTexts/TextTest.php create mode 100644 tests/Functional/Elements/RichTexts/UserTest.php create mode 100644 tests/Functional/Elements/RichTexts/UsergroupTest.php create mode 100644 tests/Functional/Parts/MentionStyleTest.php create mode 100644 tests/Functional/Parts/StyleTest.php create mode 100644 tests/Functional/assets/blocks/rich_text_broadcast.json create mode 100644 tests/Functional/assets/blocks/rich_text_channel.json create mode 100644 tests/Functional/assets/blocks/rich_text_color.json create mode 100644 tests/Functional/assets/blocks/rich_text_date.json create mode 100644 tests/Functional/assets/blocks/rich_text_emoji.json create mode 100644 tests/Functional/assets/blocks/rich_text_link.json create mode 100644 tests/Functional/assets/blocks/rich_text_list.json create mode 100644 tests/Functional/assets/blocks/rich_text_preformatted.json create mode 100644 tests/Functional/assets/blocks/rich_text_quote.json create mode 100644 tests/Functional/assets/blocks/rich_text_section_basic.json create mode 100644 tests/Functional/assets/blocks/rich_text_section_bold.json create mode 100644 tests/Functional/assets/blocks/rich_text_section_italic.json create mode 100644 tests/Functional/assets/blocks/rich_text_section_strikethrough.json create mode 100644 tests/Functional/assets/blocks/rich_text_text.json create mode 100644 tests/Functional/assets/blocks/rich_text_user.json create mode 100644 tests/Functional/assets/blocks/rich_text_usergroup.json diff --git a/src/Blocks/RichText.php b/src/Blocks/RichText.php new file mode 100644 index 0000000..0e3651c --- /dev/null +++ b/src/Blocks/RichText.php @@ -0,0 +1,35 @@ + $elements + */ + public function __construct(RichTextSubCollection|array $elements = [], ?string $blockId = null) + { + parent::__construct($blockId); + $this->elements = new RichTextSubCollection(); + $this->elements(...$elements); + } + + public function elements(RichTextSubCollection|RichTextSubElement|null ...$elements): self + { + $this->elements->append(...$elements); + + return $this; + } +} diff --git a/src/Collections/RichTextCollection.php b/src/Collections/RichTextCollection.php new file mode 100644 index 0000000..0fad853 --- /dev/null +++ b/src/Collections/RichTextCollection.php @@ -0,0 +1,51 @@ + + */ +class RichTextCollection extends ComponentCollection +{ + protected static function createComponent(array $data): Component + { + return RichTextElement::fromArray($data); + } + + /** + * @param array $elements + */ + public function __construct(array $elements = []) + { + $this->append(...$elements); + } + + public function append(RichTextElement|self|string|null ...$elements): void + { + $this->add($elements); + } + + public function prepend(RichTextElement|self|string|null ...$elements): void + { + $this->add($elements, true); + } + + protected function prepareItems(array $items): iterable + { + foreach ($items as $element) { + if ($element instanceof RichTextElement) { + yield $element; + } elseif ($element instanceof self) { + yield from $element; + } elseif (is_string($element)) { + yield Text::wrap($element); + } + } + } +} diff --git a/src/Collections/RichTextSectionCollection.php b/src/Collections/RichTextSectionCollection.php new file mode 100644 index 0000000..ee16606 --- /dev/null +++ b/src/Collections/RichTextSectionCollection.php @@ -0,0 +1,51 @@ + + */ +class RichTextSectionCollection extends ComponentCollection +{ + protected static function createComponent(array $data): Component + { + return RichTextSection::fromArray($data); + } + + /** + * @param array $sections + */ + public function __construct(array $sections = []) + { + $this->append(...$sections); + } + + public function append(RichTextSection|self|string|null ...$elements): void + { + $this->add($elements); + } + + public function prepend(RichTextSection|self|string|null ...$elements): void + { + $this->add($elements, true); + } + + + protected function prepareItems(array $items): iterable + { + foreach ($items as $section) { + if ($section instanceof RichTextSection) { + yield $section; + } elseif ($section instanceof self) { + yield from $section; + } elseif (is_string($section)) { + yield RichTextSection::wrap($section); + } + } + } +} diff --git a/src/Collections/RichTextSubCollection.php b/src/Collections/RichTextSubCollection.php new file mode 100644 index 0000000..9318a2c --- /dev/null +++ b/src/Collections/RichTextSubCollection.php @@ -0,0 +1,48 @@ + + */ +class RichTextSubCollection extends ComponentCollection +{ + protected static function createComponent(array $data): Component + { + return RichTextSubElement::fromArray($data); + } + + /** + * @param array $elements + */ + public function __construct(array $elements = []) + { + $this->append(...$elements); + } + + public function append(RichTextSubElement|self|null ...$elements): void + { + $this->add($elements); + } + + public function prepend(RichTextSubElement|self|null ...$elements): void + { + $this->add($elements, true); + } + + protected function prepareItems(array $items): iterable + { + foreach ($items as $element) { + if ($element instanceof RichTextSubElement) { + yield $element; + } elseif ($element instanceof self) { + yield from $element; + } + } + } +} diff --git a/src/Collections/TextCollection.php b/src/Collections/TextCollection.php new file mode 100644 index 0000000..8a06aaa --- /dev/null +++ b/src/Collections/TextCollection.php @@ -0,0 +1,50 @@ + + */ +class TextCollection extends ComponentCollection +{ + protected static function createComponent(array $data): Component + { + return Text::fromArray($data); + } + + /** + * @param array $elements + */ + public function __construct(array $elements = []) + { + $this->append(...$elements); + } + + public function append(Text|self|string|null ...$elements): void + { + $this->add($elements); + } + + public function prepend(Text|self|string|null ...$elements): void + { + $this->add($elements, true); + } + + protected function prepareItems(array $items): iterable + { + foreach ($items as $text) { + if ($text instanceof Text) { + yield $text; + } elseif ($text instanceof self) { + yield from $text; + } elseif (is_string($text)) { + yield Text::wrap($text); + } + } + } +} diff --git a/src/Elements/RichTexts/Broadcast.php b/src/Elements/RichTexts/Broadcast.php new file mode 100644 index 0000000..043c151 --- /dev/null +++ b/src/Elements/RichTexts/Broadcast.php @@ -0,0 +1,28 @@ +range($range); + } + + public function range(Range|string|null $range): self + { + $this->range = Range::fromValue($range); + + return $this; + } +} diff --git a/src/Elements/RichTexts/Channel.php b/src/Elements/RichTexts/Channel.php new file mode 100644 index 0000000..4589322 --- /dev/null +++ b/src/Elements/RichTexts/Channel.php @@ -0,0 +1,35 @@ +channelId($channelId); + $this->style($style); + } + + public function channelId(?string $channelId): self + { + $this->channelId = $channelId; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Color.php b/src/Elements/RichTexts/Color.php new file mode 100644 index 0000000..6e1fec1 --- /dev/null +++ b/src/Elements/RichTexts/Color.php @@ -0,0 +1,29 @@ +value($value); + } + + public function value(?string $value): self + { + $this->value = $value; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Date.php b/src/Elements/RichTexts/Date.php new file mode 100644 index 0000000..5110d4f --- /dev/null +++ b/src/Elements/RichTexts/Date.php @@ -0,0 +1,67 @@ +timestamp($timestamp); + $this->format($format); + $this->url($url); + $this->fallback($fallback); + } + + public function timestamp(?int $timestamp): self + { + $this->timestamp = $timestamp; + + return $this; + } + + public function format(?string $format): self + { + $this->format = $format; + + return $this; + } + + public function url(?string $url): self + { + $this->url = $url; + + return $this; + } + + public function fallback(?string $fallback): self + { + $this->fallback = $fallback; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Emoji.php b/src/Elements/RichTexts/Emoji.php new file mode 100644 index 0000000..b0b0e48 --- /dev/null +++ b/src/Elements/RichTexts/Emoji.php @@ -0,0 +1,40 @@ +name($name); + $this->unicode($unicode); + } + + public function name(?string $name): self + { + $this->name = $name; + + return $this; + } + + public function unicode(?string $unicode): self + { + $this->unicode = $unicode; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Link.php b/src/Elements/RichTexts/Link.php new file mode 100644 index 0000000..d8c89f6 --- /dev/null +++ b/src/Elements/RichTexts/Link.php @@ -0,0 +1,68 @@ +url($url); + $this->text($text); + $this->unsafe($unsafe); + $this->style($style); + } + + public function url(?string $url): self + { + $this->url = $url; + + return $this; + } + + public function text(?string $text): self + { + $this->text = $text; + + return $this; + } + + public function unsafe(?bool $unsafe): self + { + $this->unsafe = $unsafe; + + return $this; + } + + public function style(?Style $style): self + { + $this->style = $style; + + return $this; + } +} diff --git a/src/Elements/RichTexts/ListStyle.php b/src/Elements/RichTexts/ListStyle.php new file mode 100644 index 0000000..37174e6 --- /dev/null +++ b/src/Elements/RichTexts/ListStyle.php @@ -0,0 +1,16 @@ + $elements + */ + public function __construct( + RichTextSectionCollection|array $elements = [], + ListStyle|string|null $style = null, + ?int $indent = null, + ?int $offset = null, + ?int $border = null, + ) { + parent::__construct(); + $this->elements = new RichTextSectionCollection(); + $this->elements(...$elements); + $this->style($style); + $this->indent($indent); + $this->offset($offset); + $this->border($border); + } + + + public function elements(RichTextSectionCollection|RichTextSection|string|null ...$elements): self + { + $elements = array_map(fn (mixed $el) => is_string($el) ? RichTextSection::wrap($el) : $el, $elements); + $this->elements->append(...$elements); + + return $this; + } + public function style(ListStyle|string|null $style): self + { + $this->style = ListStyle::fromValue($style); + + return $this; + } + + public function indent(?int $indent): self + { + $this->indent = $indent; + + return $this; + } + + public function offset(?int $offset): self + { + $this->offset = $offset; + + return $this; + } +} diff --git a/src/Elements/RichTexts/RichTextPreformatted.php b/src/Elements/RichTexts/RichTextPreformatted.php new file mode 100644 index 0000000..fa814f2 --- /dev/null +++ b/src/Elements/RichTexts/RichTextPreformatted.php @@ -0,0 +1,39 @@ + $elements + */ + public function __construct(TextCollection|array $elements = [], ?int $border = null) + { + parent::__construct(); + $this->elements = new TextCollection(); + $this->elements(...$elements); + $this->border($border); + } + + public function elements(TextCollection|Text|string|null ...$elements): self + { + $elements = array_map(fn (mixed $el) => is_string($el) ? Text::wrap($el) : $el, $elements); + $this->elements->append(...$elements); + + return $this; + } +} diff --git a/src/Elements/RichTexts/RichTextQuote.php b/src/Elements/RichTexts/RichTextQuote.php new file mode 100644 index 0000000..0da3efc --- /dev/null +++ b/src/Elements/RichTexts/RichTextQuote.php @@ -0,0 +1,28 @@ + $elements + */ + public function __construct(RichTextCollection|array $elements = [], ?int $border = null) + { + parent::__construct(); + $this->elements = new RichTextCollection(); + $this->elements(...$elements); + $this->border($border); + } +} diff --git a/src/Elements/RichTexts/RichTextSection.php b/src/Elements/RichTexts/RichTextSection.php new file mode 100644 index 0000000..c80ecfe --- /dev/null +++ b/src/Elements/RichTexts/RichTextSection.php @@ -0,0 +1,30 @@ + $elements + */ + public function __construct(RichTextCollection|array $elements = []) + { + parent::__construct(); + $this->elements = new RichTextCollection(); + $this->elements(...$elements); + } +} diff --git a/src/Elements/RichTexts/RichTextSubElement.php b/src/Elements/RichTexts/RichTextSubElement.php new file mode 100644 index 0000000..04aabd8 --- /dev/null +++ b/src/Elements/RichTexts/RichTextSubElement.php @@ -0,0 +1,11 @@ +text($text); + $this->style($style); + } + + public function text(?string $text): self + { + $this->text = $text; + + return $this; + } + + public function style(?Style $style): self + { + $this->style = $style; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Traits/HasBorder.php b/src/Elements/RichTexts/Traits/HasBorder.php new file mode 100644 index 0000000..b4f5fb0 --- /dev/null +++ b/src/Elements/RichTexts/Traits/HasBorder.php @@ -0,0 +1,21 @@ +border = $border; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Traits/HasMentionStyle.php b/src/Elements/RichTexts/Traits/HasMentionStyle.php new file mode 100644 index 0000000..16acf10 --- /dev/null +++ b/src/Elements/RichTexts/Traits/HasMentionStyle.php @@ -0,0 +1,21 @@ +style = $style; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Traits/HasRichTextElements.php b/src/Elements/RichTexts/Traits/HasRichTextElements.php new file mode 100644 index 0000000..2e42d3e --- /dev/null +++ b/src/Elements/RichTexts/Traits/HasRichTextElements.php @@ -0,0 +1,25 @@ + is_string($el) ? Text::wrap($el) : $el, $elements); + $this->elements->append(...$elements); + + return $this; + } +} diff --git a/src/Elements/RichTexts/User.php b/src/Elements/RichTexts/User.php new file mode 100644 index 0000000..95f55c2 --- /dev/null +++ b/src/Elements/RichTexts/User.php @@ -0,0 +1,34 @@ +userId($userId); + $this->style($style); + } + + public function userId(?string $userId): self + { + $this->userId = $userId; + + return $this; + } +} diff --git a/src/Elements/RichTexts/Usergroup.php b/src/Elements/RichTexts/Usergroup.php new file mode 100644 index 0000000..3b516ca --- /dev/null +++ b/src/Elements/RichTexts/Usergroup.php @@ -0,0 +1,34 @@ +usergroupId($usergroupId); + $this->style($style); + } + + public function usergroupId(?string $usergroupId): self + { + $this->usergroupId = $usergroupId; + + return $this; + } +} diff --git a/src/Kit.php b/src/Kit.php index 7e6d3d9..aeb359c 100644 --- a/src/Kit.php +++ b/src/Kit.php @@ -143,6 +143,13 @@ public static function input( return new Blocks\Input($label, $element, $optional, $hint, $dispatchAction, $blockId); } + public static function richText( + Collections\RichTextSubCollection|array $elements = [], + ?string $blockId = null, + ): Blocks\RichText { + return new Blocks\RichText($elements, $blockId); + } + public static function section( Parts\Text|string|null $text = null, Parts\Fields|array|null $fields = null, @@ -169,6 +176,12 @@ public static function video( #endregion #region Elements + public static function broadcast( + Elements\RichTexts\Range|string|null $range = null, + ): Elements\RichTexts\Broadcast { + return new Elements\RichTexts\Broadcast($range); + } + public static function button( ?string $actionId = null, Parts\PlainText|string|null $text = null, @@ -181,6 +194,13 @@ public static function button( return new Elements\Button($actionId, $text, $value, $style, $url, $confirm, $accessibilityLabel); } + public static function channel( + ?string $channelId = null, + ?Parts\MentionStyle $style = null, + ): Elements\RichTexts\Channel { + return new Elements\RichTexts\Channel($channelId, $style); + } + public static function channelSelectMenu( ?string $actionId = null, Parts\PlainText|string|null $placeholder = null, @@ -206,6 +226,11 @@ public static function checkboxes( return new Elements\Checkboxes($actionId, $options, $initialOptions, $confirm, $focusOnLoad); } + public static function color(?string $value = null): Elements\RichTexts\Color + { + return new Elements\RichTexts\Color($value); + } + public static function conversationSelectMenu( ?string $actionId = null, Parts\PlainText|string|null $placeholder = null, @@ -219,6 +244,15 @@ public static function conversationSelectMenu( return new Elements\Selects\ConversationSelectMenu($actionId, $placeholder, $initialConversation, $responseUrlEnabled, $defaultToCurrentConversation, $filter, $confirm, $focusOnLoad); } + public static function date( + ?int $timestamp = null, + ?string $format = null, + ?string $url = null, + ?string $fallback = null, + ): Elements\RichTexts\Date { + return new Elements\RichTexts\Date($timestamp, $format, $url, $fallback); + } + public static function datePicker( ?string $actionId = null, \DateTime|string|null $initialDate = null, @@ -229,6 +263,11 @@ public static function datePicker( return new Elements\DatePicker($actionId, $initialDate, $placeholder, $confirm, $focusOnLoad); } + public static function emoji(?string $name = null, ?string $unicode = null): Elements\RichTexts\Emoji + { + return new Elements\RichTexts\Emoji($name, $unicode); + } + public static function externalSelectMenu( ?string $actionId = null, Parts\PlainText|string|null $placeholder = null, @@ -245,6 +284,15 @@ public static function image(?string $imageUrl = null, ?string $altText = null): return new Elements\Image($imageUrl, $altText); } + public static function link( + ?string $url = null, + ?string $text = null, + ?bool $unsafe = null, + ?Parts\Style $style = null, + ): Elements\RichTexts\Link { + return new Elements\RichTexts\Link($url, $text, $unsafe, $style); + } + /** * @param string[]|null $initialChannels */ @@ -322,6 +370,19 @@ public static function multiUserSelectMenu( return new Elements\Selects\MultiUserSelectMenu($actionId, $placeholder, $initialUsers, $maxSelectedItems, $confirm, $focusOnLoad); } + public static function numberInput( + ?string $actionId = null, + ?bool $allowDecimal = null, + int|float|string|null $maxValue = null, + int|float|string|null $minValue = null, + int|float|string|null $initialValue = null, + Parts\PlainText|string|null $placeholder = null, + ?bool $focusOnLoad = null, + ?Parts\DispatchActionConfig $dispatchActionConfig = null, + ): Elements\NumberInput { + return new Elements\NumberInput($actionId, $allowDecimal, $maxValue, $minValue, $initialValue, $placeholder, $focusOnLoad, $dispatchActionConfig); + } + /** * @param Collections\OptionSet|array|array|null $options */ @@ -333,6 +394,19 @@ public static function overflowMenu( return new Elements\OverflowMenu($actionId, $options, $confirm); } + public static function plainTextInput( + ?string $actionId = null, + Parts\PlainText|string|null $placeholder = null, + ?int $maxLength = null, + ?int $minLength = null, + ?bool $multiline = null, + ?Parts\DispatchActionConfig $dispatchActionConfig = null, + ?string $initialValue = null, + ?bool $focusOnLoad = null, + ): Elements\PlainTextInput { + return new Elements\PlainTextInput($actionId, $placeholder, $maxLength, $minLength, $multiline, $dispatchActionConfig, $initialValue, $focusOnLoad); + } + /** * @param Collections\OptionSet|array|array|null $options */ @@ -346,6 +420,48 @@ public static function radioButtons( return new Elements\RadioButtons($actionId, $options, $initialOption, $confirm, $focusOnLoad); } + /** + * @param Collections\RichTextSectionCollection|array $elements + */ + public static function richTextList( + Collections\RichTextSectionCollection|array $elements = [], + Elements\RichTexts\ListStyle|string|null $style = null, + ?int $indent = null, + ?int $offset = null, + ?int $border = null, + ): Elements\RichTexts\RichTextList { + return new Elements\RichTexts\RichTextList($elements, $style, $indent, $offset, $border); + } + + /** + * @param Collections\TextCollection|array $elements + */ + public static function richTextPreformatted( + Collections\TextCollection|array $elements = [], + ?int $border = null, + ): Elements\RichTexts\RichTextPreformatted { + return new Elements\RichTexts\RichTextPreformatted($elements, $border); + } + + /** + * @param Collections\RichTextCollection|array $elements + */ + public static function richTextQuote( + Collections\RichTextCollection|array $elements = [], + ?int $border = null, + ): Elements\RichTexts\RichTextQuote { + return new Elements\RichTexts\RichTextQuote($elements, $border); + } + + /** + * @param Collections\RichTextCollection|array $elements + */ + public static function richTextSection( + Collections\RichTextCollection|array $elements = [], + ): Elements\RichTexts\RichTextSection { + return new Elements\RichTexts\RichTextSection($elements); + } + /** * @param Collections\OptionSet|array|array|null $options * @param Collections\OptionGroupCollection|array|array>|null $optionGroups @@ -362,30 +478,9 @@ public static function staticSelectMenu( return new Elements\Selects\StaticSelectMenu($actionId, $placeholder, $options, $optionGroups, $initialOption, $confirm, $focusOnLoad); } - public static function plainTextInput( - ?string $actionId = null, - Parts\PlainText|string|null $placeholder = null, - ?int $maxLength = null, - ?int $minLength = null, - ?bool $multiline = null, - ?Parts\DispatchActionConfig $dispatchActionConfig = null, - ?string $initialValue = null, - ?bool $focusOnLoad = null, - ): Elements\PlainTextInput { - return new Elements\PlainTextInput($actionId, $placeholder, $maxLength, $minLength, $multiline, $dispatchActionConfig, $initialValue, $focusOnLoad); - } - - public static function numberInput( - ?string $actionId = null, - ?bool $allowDecimal = null, - int|float|string|null $maxValue = null, - int|float|string|null $minValue = null, - int|float|string|null $initialValue = null, - Parts\PlainText|string|null $placeholder = null, - ?bool $focusOnLoad = null, - ?Parts\DispatchActionConfig $dispatchActionConfig = null, - ): Elements\NumberInput { - return new Elements\NumberInput($actionId, $allowDecimal, $maxValue, $minValue, $initialValue, $placeholder, $focusOnLoad, $dispatchActionConfig); + public static function text(?string $text = null, ?Parts\Style $style = null): Elements\RichTexts\Text + { + return new Elements\RichTexts\Text($text, $style); } public static function timePicker( @@ -398,6 +493,18 @@ public static function timePicker( return new Elements\TimePicker($actionId, $initialTime, $placeholder, $confirm, $focusOnLoad); } + public static function user(?string $userId = null, ?Parts\MentionStyle $style = null): Elements\RichTexts\User + { + return new Elements\RichTexts\User($userId, $style); + } + + public static function usergroup( + ?string $usergroupId = null, + ?Parts\MentionStyle $style = null, + ): Elements\RichTexts\Usergroup { + return new Elements\RichTexts\Usergroup($usergroupId, $style); + } + public static function userSelectMenu( ?string $actionId = null, Parts\PlainText|string|null $placeholder = null, @@ -463,6 +570,17 @@ public static function filter( return new Parts\Filter($include, $excludeExternalSharedChannels, $excludeBotUsers); } + public static function mentionStyle( + ?bool $bold = null, + ?bool $italic = null, + ?bool $strike = null, + ?bool $highlight = null, + ?bool $clientHighlight = null, + ?bool $unlink = null, + ): Parts\MentionStyle { + return new Parts\MentionStyle($bold, $italic, $strike, $highlight, $clientHighlight, $unlink); + } + public static function mrkdwnText(?string $text = null, ?bool $verbatim = null): Parts\MrkdwnText { return new Parts\MrkdwnText($text, $verbatim); @@ -489,6 +607,15 @@ public static function plainText(?string $text = null, ?bool $emoji = null): Par { return new Parts\PlainText($text, $emoji); } + + public static function style( + ?bool $bold = null, + ?bool $italic = null, + ?bool $strike = null, + ?bool $code = null, + ): Parts\Style { + return new Parts\Style($bold, $italic, $strike, $code); + } #endregion #region Collections @@ -539,6 +666,38 @@ public static function optionSet(array $options = []): Collections\OptionSet { return new Collections\OptionSet($options); } + + /** + * @param array $elements + */ + public static function richTextCollection(array $elements = []): Collections\RichTextCollection + { + return new Collections\RichTextCollection($elements); + } + + /** + * @param array $sections + */ + public static function richTextSectionCollection(array $sections = []): Collections\RichTextSectionCollection + { + return new Collections\RichTextSectionCollection($sections); + } + + /** + * @param array $elements + */ + public static function richTextSubCollection(array $elements = []): Collections\RichTextSubCollection + { + return new Collections\RichTextSubCollection($elements); + } + + /** + * @param array $elements + */ + public static function textCollection(array $elements = []): Collections\TextCollection + { + return new Collections\TextCollection($elements); + } #endregion #region Virtual blocks diff --git a/src/Parts/MentionStyle.php b/src/Parts/MentionStyle.php new file mode 100644 index 0000000..3d0dc46 --- /dev/null +++ b/src/Parts/MentionStyle.php @@ -0,0 +1,67 @@ +bold($bold); + $this->italic($italic); + $this->strike($strike); + $this->highlight($highlight); + $this->clientHighlight($clientHighlight); + $this->unlink($unlink); + } + + public function highlight(?bool $highlight): self + { + $this->highlight = $highlight; + + return $this; + } + + public function clientHighlight(?bool $clientHighlight): self + { + $this->clientHighlight = $clientHighlight; + + return $this; + } + + public function unlink(?bool $unlink): self + { + $this->unlink = $unlink; + + return $this; + } +} diff --git a/src/Parts/Style.php b/src/Parts/Style.php new file mode 100644 index 0000000..a2c6e69 --- /dev/null +++ b/src/Parts/Style.php @@ -0,0 +1,43 @@ +bold($bold); + $this->italic($italic); + $this->strike($strike); + $this->code($code); + } + + public function code(?bool $code): self + { + $this->code = $code; + + return $this; + } +} diff --git a/src/Parts/Traits/HasBold.php b/src/Parts/Traits/HasBold.php new file mode 100644 index 0000000..0b67649 --- /dev/null +++ b/src/Parts/Traits/HasBold.php @@ -0,0 +1,20 @@ +bold = $bold; + + return $this; + } +} diff --git a/src/Parts/Traits/HasItalic.php b/src/Parts/Traits/HasItalic.php new file mode 100644 index 0000000..73f3fb1 --- /dev/null +++ b/src/Parts/Traits/HasItalic.php @@ -0,0 +1,20 @@ +italic = $italic; + + return $this; + } +} diff --git a/src/Parts/Traits/HasStrike.php b/src/Parts/Traits/HasStrike.php new file mode 100644 index 0000000..9840d33 --- /dev/null +++ b/src/Parts/Traits/HasStrike.php @@ -0,0 +1,20 @@ +strike = $strike; + + return $this; + } +} diff --git a/src/Type.php b/src/Type.php index 913dfb7..f189ce1 100644 --- a/src/Type.php +++ b/src/Type.php @@ -4,9 +4,9 @@ namespace SlackPhp\BlockKit; -use SlackPhp\BlockKit\{Blocks, Elements, Parts, Surfaces}; use SlackPhp\BlockKit\Blocks\Virtual; use SlackPhp\BlockKit\Elements\Selects; +use SlackPhp\BlockKit\Elements\RichTexts; enum Type: string { @@ -26,14 +26,21 @@ enum Type: string case FILE = 'file'; case HEADER = 'header'; case INPUT = 'input'; + case RICH_TEXT = 'rich_text'; case SECTION = 'section'; case VIDEO = 'video'; // Elements + case BROADCAST = 'broadcast'; case BUTTON = 'button'; + case CHANNEL = 'channel'; case CHECKBOXES = 'checkboxes'; + case COLOR = 'color'; + case DATE = 'date'; case DATEPICKER = 'datepicker'; + case EMOJI = 'emoji'; case IMAGE = 'image'; + case LINK = 'link'; case MULTI_SELECT_CHANNELS = 'multi_channels_select'; case MULTI_SELECT_CONVERSATIONS = 'multi_conversations_select'; case MULTI_SELECT_EXTERNAL = 'multi_external_select'; @@ -43,22 +50,31 @@ enum Type: string case OVERFLOW_MENU = 'overflow'; case PLAIN_TEXT_INPUT = 'plain_text_input'; case RADIO_BUTTONS = 'radio_buttons'; + case RICH_TEXT_LIST = 'rich_text_list'; + case RICH_TEXT_PREFORMATTED = 'rich_text_preformatted'; + case RICH_TEXT_QUOTE = 'rich_text_quote'; + case RICH_TEXT_SECTION = 'rich_text_section'; case SELECT_CHANNELS = 'channels_select'; case SELECT_CONVERSATIONS = 'conversations_select'; case SELECT_EXTERNAL = 'external_select'; case SELECT_STATIC = 'static_select'; case SELECT_USERS = 'users_select'; + case TEXT = 'text'; case TIMEPICKER = 'timepicker'; + case USER = 'user'; + case USERGROUP = 'usergroup'; // Parts (aka Composition Objects) case CONFIRM = 'confirm'; case DISPATCH_ACTION_CONFIG = 'dispatch_action_config'; case FIELDS = 'fields'; case FILTER = 'filter'; + case MENTION_STYLE = 'mention_style'; case MRKDWNTEXT = 'mrkdwn'; case OPTION = 'option'; case OPTION_GROUP = 'option_group'; case PLAINTEXT = 'plain_text'; + case STYLE = 'style'; /** @var array */ private const TYPE_MAP = [ @@ -78,6 +94,7 @@ enum Type: string Blocks\File::class => self::FILE, Blocks\Header::class => self::HEADER, Blocks\Input::class => self::INPUT, + Blocks\RichText::class => self::RICH_TEXT, Blocks\Section::class => self::SECTION, Blocks\Video::class => self::VIDEO, @@ -108,15 +125,32 @@ enum Type: string Selects\StaticSelectMenu::class => self::SELECT_STATIC, Selects\UserSelectMenu::class => self::SELECT_USERS, + // Rich Texts + RichTexts\Broadcast::class => self::BROADCAST, + RichTexts\Channel::class => self::CHANNEL, + RichTexts\Color::class => self::COLOR, + RichTexts\Date::class => self::DATE, + RichTexts\Emoji::class => self::EMOJI, + RichTexts\Link::class => self::LINK, + RichTexts\RichTextList::class => self::RICH_TEXT_LIST, + RichTexts\RichTextPreformatted::class => self::RICH_TEXT_PREFORMATTED, + RichTexts\RichTextQuote::class => self::RICH_TEXT_QUOTE, + RichTexts\RichTextSection::class => self::RICH_TEXT_SECTION, + RichTexts\Text::class => self::TEXT, + RichTexts\User::class => self::USER, + RichTexts\Usergroup::class => self::USERGROUP, + // Parts (aka Composition Objects) Parts\Confirm::class => self::CONFIRM, Parts\DispatchActionConfig::class => self::DISPATCH_ACTION_CONFIG, Parts\Fields::class => self::FIELDS, Parts\Filter::class => self::FILTER, + Parts\MentionStyle::class => self::MENTION_STYLE, Parts\MrkdwnText::class => self::MRKDWNTEXT, Parts\Option::class => self::OPTION, Parts\OptionGroup::class => self::OPTION_GROUP, Parts\PlainText::class => self::PLAINTEXT, + Parts\Style::class => self::STYLE, ]; public static function fromClass(string $class): self diff --git a/tests/Functional/BlockTest.php b/tests/Functional/BlockTest.php index 70689c1..0b56e64 100644 --- a/tests/Functional/BlockTest.php +++ b/tests/Functional/BlockTest.php @@ -3,6 +3,7 @@ namespace SlackPhp\BlockKit\Tests\Functional; use SlackPhp\BlockKit\Blocks\Block; +use SlackPhp\BlockKit\Blocks\RichText; use SlackPhp\BlockKit\Blocks\Video; class BlockTest extends TestCase @@ -26,6 +27,22 @@ public function testMatchesJsonFromReferenceDocumentation(string $file, string $ public static function providesJsonFiles(): array { return [ + ['blocks/rich_text_broadcast', RichText::class], + ['blocks/rich_text_channel', RichText::class], + ['blocks/rich_text_color', RichText::class], + ['blocks/rich_text_date', RichText::class], + ['blocks/rich_text_emoji', RichText::class], + ['blocks/rich_text_link', RichText::class], + ['blocks/rich_text_list', RichText::class], + ['blocks/rich_text_preformatted', RichText::class], + ['blocks/rich_text_quote', RichText::class], + ['blocks/rich_text_section_basic', RichText::class], + ['blocks/rich_text_section_bold', RichText::class], + ['blocks/rich_text_section_italic', RichText::class], + ['blocks/rich_text_section_strikethrough', RichText::class], + ['blocks/rich_text_text', RichText::class], + ['blocks/rich_text_user', RichText::class], + ['blocks/rich_text_usergroup', RichText::class], ['blocks/video', Video::class], ]; } diff --git a/tests/Functional/Collections/RichTextCollectionTest.php b/tests/Functional/Collections/RichTextCollectionTest.php new file mode 100644 index 0000000..040901b --- /dev/null +++ b/tests/Functional/Collections/RichTextCollectionTest.php @@ -0,0 +1,37 @@ + 'text', + 'text' => 'Hello there, ', + ], + [ + 'type' => 'text', + 'text' => 'I am a basic rich text!', + ], + ]; + + $properties = new RichTextCollection(); + $properties->append('Hello there, '); + $properties->append('I am a basic rich text!'); + + $constructor = new RichTextCollection(['Hello there, ', 'I am a basic rich text!']); + $kit = Kit::richTextCollection(['Hello there, ', 'I am a basic rich text!']); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Collections/RichTextSectionCollectionTest.php b/tests/Functional/Collections/RichTextSectionCollectionTest.php new file mode 100644 index 0000000..7ac6feb --- /dev/null +++ b/tests/Functional/Collections/RichTextSectionCollectionTest.php @@ -0,0 +1,57 @@ + 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Huddles', + ], + ], + ], + [ + 'type' => 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Canvas', + ], + ], + ], + [ + 'type' => 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Developing with Block Kit', + ], + ], + ], + ]; + + $properties = new RichTextSectionCollection(); + $properties->append('Huddles'); + $properties->append('Canvas'); + $properties->append('Developing with Block Kit'); + + $constructor = new RichTextSectionCollection(['Huddles', 'Canvas', 'Developing with Block Kit']); + $kit = Kit::richTextSectionCollection(['Huddles', 'Canvas', 'Developing with Block Kit']); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Collections/RichTextSubCollectionTest.php b/tests/Functional/Collections/RichTextSubCollectionTest.php new file mode 100644 index 0000000..4712c9e --- /dev/null +++ b/tests/Functional/Collections/RichTextSubCollectionTest.php @@ -0,0 +1,63 @@ + 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Hello there, I am a basic rich text!', + ], + ], + ], + [ + 'type' => 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Hello there, ', + ], + [ + 'type' => 'text', + 'text' => 'I am a bold rich text!', + 'style' => [ + 'bold' => true, + ], + ], + ], + ], + ]; + + $basic = new RichTextSection(['Hello there, I am a basic rich text!']); + $bold = new RichTextSection([ + 'Hello there, ', + new Text('I am a bold rich text!', new Style(true)), + ]); + + $properties = new RichTextSubCollection(); + $properties->append($basic); + $properties->append($bold); + + $constructor = new RichTextSubCollection([$basic, $bold]); + $kit = Kit::richTextSubCollection([$basic, $bold]); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Collections/TextCollectionTest.php b/tests/Functional/Collections/TextCollectionTest.php new file mode 100644 index 0000000..b02311e --- /dev/null +++ b/tests/Functional/Collections/TextCollectionTest.php @@ -0,0 +1,45 @@ + 'text', + 'text' => 'Hello there, ', + ], + [ + 'type' => 'text', + 'text' => 'I am a bold rich text!', + 'style' => [ + 'bold' => true, + ], + ], + ]; + + $basic = 'Hello there, '; + $bold = new Text('I am a bold rich text!', new Style(true)); + + $properties = new TextCollection(); + $properties->append($basic); + $properties->append($bold); + + $constructor = new TextCollection([$basic, $bold]); + $kit = Kit::textCollection([$basic, $bold]); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/BroadcastTest.php b/tests/Functional/Elements/RichTexts/BroadcastTest.php new file mode 100644 index 0000000..929967e --- /dev/null +++ b/tests/Functional/Elements/RichTexts/BroadcastTest.php @@ -0,0 +1,38 @@ + 'broadcast', + 'range' => 'channel', + ]; + + $properties = new Broadcast(); + $properties->range = Range::CHANNEL; + + $constructor = new Broadcast(Range::CHANNEL); + $fluent = Broadcast::new()->range(Range::CHANNEL); + $kit = Kit::broadcast(Range::CHANNEL); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/ChannelTest.php b/tests/Functional/Elements/RichTexts/ChannelTest.php new file mode 100644 index 0000000..639fa97 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/ChannelTest.php @@ -0,0 +1,74 @@ + 'channel', + 'channel_id' => 'U123ABC456', + ]; + + $properties = new Channel(); + $properties->channelId = 'U123ABC456'; + + $constructor = new Channel('U123ABC456'); + $fluent = Channel::new()->channelId('U123ABC456'); + $kit = Kit::channel('U123ABC456'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedChannels(): void + { + $expected = [ + 'type' => 'channel', + 'channel_id' => 'U123ABC456', + 'style' => [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'highlight' => true, + 'client_highlight' => true, + 'unlink' => true, + ], + ]; + + $style = new MentionStyle(true, true, true, true, true, true); + + $properties = new Channel(); + $properties->channelId = 'U123ABC456'; + $properties->style = $style; + + $constructor = new Channel('U123ABC456', $style); + $fluent = Channel::new()->channelId('U123ABC456')->style($style); + $kit = Kit::channel('U123ABC456', $style); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/ColorTest.php b/tests/Functional/Elements/RichTexts/ColorTest.php new file mode 100644 index 0000000..2e85058 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/ColorTest.php @@ -0,0 +1,37 @@ + 'color', + 'value' => '#F405B3', + ]; + + $properties = new Color(); + $properties->value = '#F405B3'; + + $constructor = new Color('#F405B3'); + $fluent = Color::new()->value('#F405B3'); + $kit = Kit::color('#F405B3'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/DateTest.php b/tests/Functional/Elements/RichTexts/DateTest.php new file mode 100644 index 0000000..2f0adf4 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/DateTest.php @@ -0,0 +1,86 @@ + 'date', + 'timestamp' => 1720710212, + 'format' => '{date_num} at {time}', + ]; + + $properties = new Date(); + $properties->timestamp = 1720710212; + $properties->format = '{date_num} at {time}'; + + $constructor = new Date(1720710212, '{date_num} at {time}'); + $fluent = Date::new()->timestamp(1720710212)->format('{date_num} at {time}'); + $kit = Kit::date(1720710212, '{date_num} at {time}'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedDates(): void + { + $expected = [ + 'type' => 'date', + 'timestamp' => 1720710212, + 'format' => '{date_num} at {time}', + 'url' => 'https://api.slack.com', + 'fallback' => 'Current time', + ]; + + $properties = new Date(); + $properties->timestamp = 1720710212; + $properties->format = '{date_num} at {time}'; + $properties->url = 'https://api.slack.com'; + $properties->fallback = 'Current time'; + + $constructor = new Date( + 1720710212, + '{date_num} at {time}', + 'https://api.slack.com', + 'Current time', + ); + + $fluent = Date::new() + ->timestamp(1720710212) + ->format('{date_num} at {time}') + ->url('https://api.slack.com') + ->fallback('Current time'); + + $kit = Kit::date( + 1720710212, + '{date_num} at {time}', + 'https://api.slack.com', + 'Current time', + ); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/EmojiTest.php b/tests/Functional/Elements/RichTexts/EmojiTest.php new file mode 100644 index 0000000..415eef4 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/EmojiTest.php @@ -0,0 +1,64 @@ + 'emoji', + 'name' => 'basketball', + ]; + + $properties = new Emoji(); + $properties->name = 'basketball'; + + $constructor = new Emoji('basketball'); + $fluent = Emoji::new()->name('basketball'); + $kit = Kit::emoji('basketball'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedEmojis(): void + { + $expected = [ + 'type' => 'emoji', + 'name' => 'basketball', + 'unicode' => '\uD83C\uDFC0', + ]; + + $properties = new Emoji(); + $properties->name = 'basketball'; + $properties->unicode = '\uD83C\uDFC0'; + + $constructor = new Emoji('basketball', '\uD83C\uDFC0'); + $fluent = Emoji::new()->name('basketball')->unicode('\uD83C\uDFC0'); + $kit = Kit::emoji('basketball', '\uD83C\uDFC0'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/LinkTest.php b/tests/Functional/Elements/RichTexts/LinkTest.php new file mode 100644 index 0000000..021546c --- /dev/null +++ b/tests/Functional/Elements/RichTexts/LinkTest.php @@ -0,0 +1,92 @@ + 'link', + 'url' => 'https://api.slack.com', + ]; + + $properties = new Link(); + $properties->url = 'https://api.slack.com'; + + $constructor = new Link('https://api.slack.com'); + $fluent = Link::new()->url('https://api.slack.com'); + $kit = Kit::link('https://api.slack.com'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedLinks(): void + { + $expected = [ + 'type' => 'link', + 'url' => 'https://api.slack.com', + 'text' => 'Unlock your productivity potential with Slack Platform | Slack', + 'unsafe' => false, + 'style' => [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'code' => true, + ], + ]; + + $style = new Style(true, true, true, true); + + $properties = new Link(); + $properties->url = 'https://api.slack.com'; + $properties->text = 'Unlock your productivity potential with Slack Platform | Slack'; + $properties->unsafe = false; + $properties->style = $style; + + $constructor = new Link( + 'https://api.slack.com', + 'Unlock your productivity potential with Slack Platform | Slack', + false, + $style, + ); + + $fluent = Link::new() + ->url('https://api.slack.com') + ->text('Unlock your productivity potential with Slack Platform | Slack') + ->unsafe(false) + ->style($style); + + $kit = Kit::link( + 'https://api.slack.com', + 'Unlock your productivity potential with Slack Platform | Slack', + false, + $style, + ); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/ListStyleTest.php b/tests/Functional/Elements/RichTexts/ListStyleTest.php new file mode 100644 index 0000000..6402696 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/ListStyleTest.php @@ -0,0 +1,22 @@ + 'rich_text_list', + 'style' => 'bullet', + 'elements' => [ + [ + 'type' => 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Hashbrowns', + ], + ], + ], + [ + 'type' => 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Eggs', + ], + ], + ], + ], + ]; + + $hashbrowns = new RichTextSection(['Hashbrowns']); + $eggs = new RichTextSection(['Eggs']); + + $properties = new RichTextList(); + $properties->elements->append($hashbrowns, $eggs); + $properties->style = ListStyle::BULLET; + + $constructor = new RichTextList([$hashbrowns, $eggs], ListStyle::BULLET); + $fluent = RichTextList::new()->elements($hashbrowns, $eggs)->style(ListStyle::BULLET); + $kit = Kit::richTextList([$hashbrowns, $eggs], ListStyle::BULLET); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/RichTextPreformattedTest.php b/tests/Functional/Elements/RichTexts/RichTextPreformattedTest.php new file mode 100644 index 0000000..4216c4f --- /dev/null +++ b/tests/Functional/Elements/RichTexts/RichTextPreformattedTest.php @@ -0,0 +1,78 @@ + 'rich_text_preformatted', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'composer require slack-php/slack-block-kit', + ], + ], + ]; + + $properties = new RichTextPreformatted(); + $properties->elements->append('composer require slack-php/slack-block-kit'); + + $constructor = new RichTextPreformatted(['composer require slack-php/slack-block-kit']); + $fluent = RichTextPreformatted::new()->elements('composer require slack-php/slack-block-kit'); + $kit = Kit::richTextPreformatted(['composer require slack-php/slack-block-kit']); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedRichTextPreformatted(): void + { + $expected = [ + 'type' => 'rich_text_preformatted', + 'border' => 1, + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'composer require slack-php/slack-block-kit', + ], + ], + ]; + + $properties = new RichTextPreformatted(); + $properties->elements->append('composer require slack-php/slack-block-kit'); + $properties->border = 1; + + $constructor = new RichTextPreformatted(['composer require slack-php/slack-block-kit'], 1); + + $fluent = RichTextPreformatted::new() + ->elements('composer require slack-php/slack-block-kit') + ->border(1); + + $kit = Kit::richTextPreformatted(['composer require slack-php/slack-block-kit'], 1); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/RichTextQuoteTest.php b/tests/Functional/Elements/RichTexts/RichTextQuoteTest.php new file mode 100644 index 0000000..ffd1d26 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/RichTextQuoteTest.php @@ -0,0 +1,78 @@ + 'rich_text_quote', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'What we need is good examples in our documentation.', + ], + ], + ]; + + $properties = new RichTextQuote(); + $properties->elements->append('What we need is good examples in our documentation.'); + + $constructor = new RichTextQuote(['What we need is good examples in our documentation.']); + $fluent = RichTextQuote::new()->elements('What we need is good examples in our documentation.'); + $kit = Kit::richTextQuote(['What we need is good examples in our documentation.']); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedRichTextQuotes(): void + { + $expected = [ + 'type' => 'rich_text_quote', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'What we need is good examples in our documentation.', + ], + ], + 'border' => 1, + ]; + + $properties = new RichTextQuote(); + $properties->elements->append('What we need is good examples in our documentation.'); + $properties->border = 1; + + $constructor = new RichTextQuote(['What we need is good examples in our documentation.'], 1); + + $fluent = RichTextQuote::new() + ->elements('What we need is good examples in our documentation.') + ->border(1); + + $kit = Kit::richTextQuote(['What we need is good examples in our documentation.'], 1); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/RichTextSectionTest.php b/tests/Functional/Elements/RichTexts/RichTextSectionTest.php new file mode 100644 index 0000000..00cc53c --- /dev/null +++ b/tests/Functional/Elements/RichTexts/RichTextSectionTest.php @@ -0,0 +1,54 @@ + 'rich_text_section', + 'elements' => [ + [ + 'type' => 'text', + 'text' => 'Hello there, ', + ], + [ + 'type' => 'text', + 'text' => 'I am a bold rich text block!', + 'style' => [ + 'bold' => true, + ], + ], + ], + ]; + + $basic = new Text('Hello there, '); + $bold = new Text('I am a bold rich text block!', new Style(true)); + + $properties = new RichTextSection(); + $properties->elements->append($basic, $bold); + + $constructor = new RichTextSection([$basic, $bold]); + $fluent = RichTextSection::new()->elements($basic, $bold); + $kit = Kit::richTextSection([$basic, $bold]); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/TextTest.php b/tests/Functional/Elements/RichTexts/TextTest.php new file mode 100644 index 0000000..327bd26 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/TextTest.php @@ -0,0 +1,72 @@ + 'text', + 'text' => 'I am a regular rich text!', + ]; + + $properties = new Text(); + $properties->text = 'I am a regular rich text!'; + + $constructor = new Text('I am a regular rich text!'); + $fluent = Text::new()->text('I am a regular rich text!'); + $kit = Kit::text('I am a regular rich text!'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedTexts(): void + { + $expected = [ + 'type' => 'text', + 'text' => 'I am a fully styled rich text!', + 'style' => [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'code' => true, + ], + ]; + + $style = new Style(true, true, true, true); + + $properties = new Text(); + $properties->text = 'I am a fully styled rich text!'; + $properties->style = $style; + + $constructor = new Text('I am a fully styled rich text!', $style); + $fluent = Text::new()->text('I am a fully styled rich text!')->style($style); + $kit = Kit::text('I am a fully styled rich text!', $style); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/UserTest.php b/tests/Functional/Elements/RichTexts/UserTest.php new file mode 100644 index 0000000..b901b8e --- /dev/null +++ b/tests/Functional/Elements/RichTexts/UserTest.php @@ -0,0 +1,74 @@ + 'user', + 'user_id' => 'U123ABC456', + ]; + + $properties = new User(); + $properties->userId = 'U123ABC456'; + + $constructor = new User('U123ABC456'); + $fluent = User::new()->userId('U123ABC456'); + $kit = Kit::user('U123ABC456'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedUsers(): void + { + $expected = [ + 'type' => 'user', + 'user_id' => 'U123ABC456', + 'style' => [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'highlight' => true, + 'client_highlight' => true, + 'unlink' => true, + ], + ]; + + $style = new MentionStyle(true, true, true, true, true, true); + + $properties = new User(); + $properties->userId = 'U123ABC456'; + $properties->style = $style; + + $constructor = new User('U123ABC456', $style); + $fluent = User::new()->userId('U123ABC456')->style($style); + $kit = Kit::user('U123ABC456', $style); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Elements/RichTexts/UsergroupTest.php b/tests/Functional/Elements/RichTexts/UsergroupTest.php new file mode 100644 index 0000000..310f446 --- /dev/null +++ b/tests/Functional/Elements/RichTexts/UsergroupTest.php @@ -0,0 +1,74 @@ + 'usergroup', + 'usergroup_id' => 'G123ABC456', + ]; + + $properties = new Usergroup(); + $properties->usergroupId = 'G123ABC456'; + + $constructor = new Usergroup('G123ABC456'); + $fluent = Usergroup::new()->usergroupId('G123ABC456'); + $kit = Kit::usergroup('G123ABC456'); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedUsergroups(): void + { + $expected = [ + 'type' => 'usergroup', + 'usergroup_id' => 'G123ABC456', + 'style' => [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'highlight' => true, + 'client_highlight' => true, + 'unlink' => true, + ], + ]; + + $style = new MentionStyle(true, true, true, true, true, true); + + $properties = new Usergroup(); + $properties->usergroupId = 'G123ABC456'; + $properties->style = $style; + + $constructor = new Usergroup('G123ABC456', $style); + $fluent = Usergroup::new()->usergroupId('G123ABC456')->style($style); + $kit = Kit::usergroup('G123ABC456', $style); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Parts/MentionStyleTest.php b/tests/Functional/Parts/MentionStyleTest.php new file mode 100644 index 0000000..ea23c1b --- /dev/null +++ b/tests/Functional/Parts/MentionStyleTest.php @@ -0,0 +1,71 @@ +validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedMentionStyles(): void + { + $expected = [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'highlight' => true, + 'client_highlight' => true, + 'unlink' => true, + ]; + + $properties = new MentionStyle(); + $properties->bold = true; + $properties->italic = true; + $properties->strike = true; + $properties->highlight = true; + $properties->clientHighlight = true; + $properties->unlink = true; + + $constructor = new MentionStyle(true, true, true, true, true, true); + + $fluent = MentionStyle::new() + ->bold(true) + ->italic(true) + ->strike(true) + ->highlight(true) + ->clientHighlight(true) + ->unlink(true); + + $kit = Kit::mentionStyle(true, true, true, true, true, true); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/Parts/StyleTest.php b/tests/Functional/Parts/StyleTest.php new file mode 100644 index 0000000..19a9239 --- /dev/null +++ b/tests/Functional/Parts/StyleTest.php @@ -0,0 +1,65 @@ +validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } + + public function testItCreatesAdvancedStyles(): void + { + $expected = [ + 'bold' => true, + 'italic' => true, + 'strike' => true, + 'code' => true, + ]; + + $properties = new Style(); + $properties->bold = true; + $properties->italic = true; + $properties->strike = true; + $properties->code = true; + + $constructor = new Style(true, true, true, true); + + $fluent = Style::new() + ->bold(true) + ->italic(true) + ->strike(true) + ->code(true); + + $kit = Kit::style(true, true, true, true); + + $properties->validate(); + $constructor->validate(); + $fluent->validate(); + $kit->validate(); + + self::assertEquals($expected, $properties->toArray()); + self::assertEquals($expected, $constructor->toArray()); + self::assertEquals($expected, $fluent->toArray()); + self::assertEquals($expected, $kit->toArray()); + } +} diff --git a/tests/Functional/assets/blocks/rich_text_broadcast.json b/tests/Functional/assets/blocks/rich_text_broadcast.json new file mode 100644 index 0000000..1da37d4 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_broadcast.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "broadcast", + "range": "everyone" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_channel.json b/tests/Functional/assets/blocks/rich_text_channel.json new file mode 100644 index 0000000..fe9e9af --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_channel.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "channel", + "channel_id": "C123ABC456" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_color.json b/tests/Functional/assets/blocks/rich_text_color.json new file mode 100644 index 0000000..6d89e84 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_color.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "color", + "value": "#F405B3" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_date.json b/tests/Functional/assets/blocks/rich_text_date.json new file mode 100644 index 0000000..18c941b --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_date.json @@ -0,0 +1,16 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "date", + "timestamp": 1720710212, + "format": "{date_num} at {time}", + "fallback": "timey" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_emoji.json b/tests/Functional/assets/blocks/rich_text_emoji.json new file mode 100644 index 0000000..39343dd --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_emoji.json @@ -0,0 +1,30 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "emoji", + "name": "basketball" + }, + { + "type": "text", + "text": " " + }, + { + "type": "emoji", + "name": "snowboarder" + }, + { + "type": "text", + "text": " " + }, + { + "type": "emoji", + "name": "checkered_flag" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_link.json b/tests/Functional/assets/blocks/rich_text_link.json new file mode 100644 index 0000000..79e3371 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_link.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "link", + "url": "https://api.slack.com" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_list.json b/tests/Functional/assets/blocks/rich_text_list.json new file mode 100644 index 0000000..733db13 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_list.json @@ -0,0 +1,50 @@ +{ + "type": "rich_text", + "block_id": "block1", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "My favorite Slack features (in no particular order):" + } + ] + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Huddles" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Canvas" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Developing with Block Kit" + } + ] + } + ], + "style": "bullet", + "indent": 0, + "border": 1 + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_preformatted.json b/tests/Functional/assets/blocks/rich_text_preformatted.json new file mode 100644 index 0000000..041cfbd --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_preformatted.json @@ -0,0 +1,15 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": "{\n \"object\": {\n \"description\": \"this is an example of a json object\"\n }\n}" + } + ], + "border": 0 + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_quote.json b/tests/Functional/assets/blocks/rich_text_quote.json new file mode 100644 index 0000000..1fd5ec3 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_quote.json @@ -0,0 +1,24 @@ +{ + "type": "rich_text", + "block_id": "Vrzsu", + "elements": [ + { + "type": "rich_text_quote", + "elements": [ + { + "type": "text", + "text": "What we need is good examples in our documentation." + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Yes - I completely agree, Luke!" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_section_basic.json b/tests/Functional/assets/blocks/rich_text_section_basic.json new file mode 100644 index 0000000..ee11769 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_section_basic.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Hello there, I am a basic rich text block!" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_section_bold.json b/tests/Functional/assets/blocks/rich_text_section_bold.json new file mode 100644 index 0000000..92ee24c --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_section_bold.json @@ -0,0 +1,21 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Hello there, " + }, + { + "type": "text", + "text": "I am a bold rich text block!", + "style": { + "bold": true + } + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_section_italic.json b/tests/Functional/assets/blocks/rich_text_section_italic.json new file mode 100644 index 0000000..e333990 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_section_italic.json @@ -0,0 +1,21 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Hello there, " + }, + { + "type": "text", + "text": "I am an italic rich text block!", + "style": { + "italic": true + } + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_section_strikethrough.json b/tests/Functional/assets/blocks/rich_text_section_strikethrough.json new file mode 100644 index 0000000..75a9226 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_section_strikethrough.json @@ -0,0 +1,21 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Hello there, " + }, + { + "type": "text", + "text": "I am a strikethrough rich text block!", + "style": { + "strike": true + } + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_text.json b/tests/Functional/assets/blocks/rich_text_text.json new file mode 100644 index 0000000..92ee24c --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_text.json @@ -0,0 +1,21 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Hello there, " + }, + { + "type": "text", + "text": "I am a bold rich text block!", + "style": { + "bold": true + } + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_user.json b/tests/Functional/assets/blocks/rich_text_user.json new file mode 100644 index 0000000..4a50cc4 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_user.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "user", + "user_id": "U123ABC456" + } + ] + } + ] +} diff --git a/tests/Functional/assets/blocks/rich_text_usergroup.json b/tests/Functional/assets/blocks/rich_text_usergroup.json new file mode 100644 index 0000000..9959094 --- /dev/null +++ b/tests/Functional/assets/blocks/rich_text_usergroup.json @@ -0,0 +1,14 @@ +{ + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "usergroup", + "usergroup_id": "G123ABC456" + } + ] + } + ] +} From e7a7ab2a58029639473fbaf03cdd83855452cbbc Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Tue, 24 Dec 2024 22:39:19 +0100 Subject: [PATCH 2/3] chore: add `@see` docblock urls to Slack block kit documentation --- src/Blocks/RichText.php | 3 ++ src/Elements/RichTexts/Broadcast.php | 3 ++ src/Elements/RichTexts/Channel.php | 3 ++ src/Elements/RichTexts/Color.php | 3 ++ src/Elements/RichTexts/Date.php | 3 ++ src/Elements/RichTexts/Emoji.php | 3 ++ src/Elements/RichTexts/Link.php | 3 ++ src/Elements/RichTexts/RichTextList.php | 3 ++ .../RichTexts/RichTextPreformatted.php | 3 ++ src/Elements/RichTexts/RichTextQuote.php | 3 ++ src/Elements/RichTexts/RichTextSection.php | 3 ++ src/Elements/RichTexts/Text.php | 3 ++ src/Elements/RichTexts/User.php | 3 ++ src/Elements/RichTexts/Usergroup.php | 3 ++ src/Kit.php | 38 +++++++++++++++++++ 15 files changed, 80 insertions(+) diff --git a/src/Blocks/RichText.php b/src/Blocks/RichText.php index 0e3651c..58e3fd3 100644 --- a/src/Blocks/RichText.php +++ b/src/Blocks/RichText.php @@ -10,6 +10,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidCollection; +/** + * @see https://api.slack.com/reference/block-kit/blocks#rich_text + */ #[RequiresAllOf('elements')] class RichText extends Block { diff --git a/src/Elements/RichTexts/Broadcast.php b/src/Elements/RichTexts/Broadcast.php index 043c151..ce23f5c 100644 --- a/src/Elements/RichTexts/Broadcast.php +++ b/src/Elements/RichTexts/Broadcast.php @@ -7,6 +7,9 @@ use SlackPhp\BlockKit\Property; use SlackPhp\BlockKit\Validation\RequiresAllOf; +/** + * @see https://api.slack.com/reference/block-kit/blocks#broadcast-element-type + */ #[RequiresAllOf('range')] class Broadcast extends RichTextElement { diff --git a/src/Elements/RichTexts/Channel.php b/src/Elements/RichTexts/Channel.php index 4589322..ddf5207 100644 --- a/src/Elements/RichTexts/Channel.php +++ b/src/Elements/RichTexts/Channel.php @@ -10,6 +10,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidString; +/** + * @see https://api.slack.com/reference/block-kit/blocks#channel-element-type + */ #[RequiresAllOf('channel_id')] class Channel extends RichTextElement { diff --git a/src/Elements/RichTexts/Color.php b/src/Elements/RichTexts/Color.php index 6e1fec1..82df584 100644 --- a/src/Elements/RichTexts/Color.php +++ b/src/Elements/RichTexts/Color.php @@ -8,6 +8,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidString; +/** + * @see https://api.slack.com/reference/block-kit/blocks#color-element-type + */ #[RequiresAllOf('value')] class Color extends RichTextElement { diff --git a/src/Elements/RichTexts/Date.php b/src/Elements/RichTexts/Date.php index 5110d4f..f95cf7e 100644 --- a/src/Elements/RichTexts/Date.php +++ b/src/Elements/RichTexts/Date.php @@ -9,6 +9,9 @@ use SlackPhp\BlockKit\Validation\ValidString; use SlackPhp\BlockKit\Validation\ValidUrl; +/** + * @see https://api.slack.com/reference/block-kit/blocks#date-element-type + */ #[RequiresAllOf('timestamp', 'format')] class Date extends RichTextElement { diff --git a/src/Elements/RichTexts/Emoji.php b/src/Elements/RichTexts/Emoji.php index b0b0e48..e3e35f2 100644 --- a/src/Elements/RichTexts/Emoji.php +++ b/src/Elements/RichTexts/Emoji.php @@ -8,6 +8,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidString; +/** + * @see https://api.slack.com/reference/block-kit/blocks#emoji-element-type + */ #[RequiresAllOf('name')] class Emoji extends RichTextElement { diff --git a/src/Elements/RichTexts/Link.php b/src/Elements/RichTexts/Link.php index d8c89f6..45cc95a 100644 --- a/src/Elements/RichTexts/Link.php +++ b/src/Elements/RichTexts/Link.php @@ -10,6 +10,9 @@ use SlackPhp\BlockKit\Validation\ValidString; use SlackPhp\BlockKit\Validation\ValidUrl; +/** + * @see https://api.slack.com/reference/block-kit/blocks#link-element-type + */ #[RequiresAllOf('url')] class Link extends RichTextElement { diff --git a/src/Elements/RichTexts/RichTextList.php b/src/Elements/RichTexts/RichTextList.php index 9cc9087..5cc38e3 100644 --- a/src/Elements/RichTexts/RichTextList.php +++ b/src/Elements/RichTexts/RichTextList.php @@ -11,6 +11,9 @@ use SlackPhp\BlockKit\Validation\ValidCollection; use SlackPhp\BlockKit\Validation\ValidInt; +/** + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_list + */ #[RequiresAllOf('elements', 'style')] class RichTextList extends RichTextSubElement { diff --git a/src/Elements/RichTexts/RichTextPreformatted.php b/src/Elements/RichTexts/RichTextPreformatted.php index fa814f2..88bcdc9 100644 --- a/src/Elements/RichTexts/RichTextPreformatted.php +++ b/src/Elements/RichTexts/RichTextPreformatted.php @@ -10,6 +10,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidCollection; +/** + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_preformatted + */ #[RequiresAllOf('elements')] class RichTextPreformatted extends RichTextSubElement { diff --git a/src/Elements/RichTexts/RichTextQuote.php b/src/Elements/RichTexts/RichTextQuote.php index 0da3efc..98f31af 100644 --- a/src/Elements/RichTexts/RichTextQuote.php +++ b/src/Elements/RichTexts/RichTextQuote.php @@ -9,6 +9,9 @@ use SlackPhp\BlockKit\Elements\RichTexts\Traits\HasRichTextElements; use SlackPhp\BlockKit\Validation\RequiresAllOf; +/** + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_quote + */ #[RequiresAllOf('elements')] class RichTextQuote extends RichTextSubElement { diff --git a/src/Elements/RichTexts/RichTextSection.php b/src/Elements/RichTexts/RichTextSection.php index c80ecfe..f600d13 100644 --- a/src/Elements/RichTexts/RichTextSection.php +++ b/src/Elements/RichTexts/RichTextSection.php @@ -8,6 +8,9 @@ use SlackPhp\BlockKit\Elements\RichTexts\Traits\HasRichTextElements; use SlackPhp\BlockKit\Validation\RequiresAllOf; +/** + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_section + */ #[RequiresAllOf('elements')] class RichTextSection extends RichTextSubElement { diff --git a/src/Elements/RichTexts/Text.php b/src/Elements/RichTexts/Text.php index 5ee0055..6a2c5b3 100644 --- a/src/Elements/RichTexts/Text.php +++ b/src/Elements/RichTexts/Text.php @@ -9,6 +9,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidString; +/** + * @see https://api.slack.com/reference/block-kit/blocks#text-element-type + */ #[RequiresAllOf('text')] class Text extends RichTextElement { diff --git a/src/Elements/RichTexts/User.php b/src/Elements/RichTexts/User.php index 95f55c2..b750285 100644 --- a/src/Elements/RichTexts/User.php +++ b/src/Elements/RichTexts/User.php @@ -10,6 +10,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidString; +/** + * @see https://api.slack.com/reference/block-kit/blocks#user-element-type + */ #[RequiresAllOf('user_id')] class User extends RichTextElement { diff --git a/src/Elements/RichTexts/Usergroup.php b/src/Elements/RichTexts/Usergroup.php index 3b516ca..9f80e43 100644 --- a/src/Elements/RichTexts/Usergroup.php +++ b/src/Elements/RichTexts/Usergroup.php @@ -10,6 +10,9 @@ use SlackPhp\BlockKit\Validation\RequiresAllOf; use SlackPhp\BlockKit\Validation\ValidString; +/** + * @see https://api.slack.com/reference/block-kit/blocks#user-group-element-type + */ #[RequiresAllOf('usergroup_id')] class Usergroup extends RichTextElement { diff --git a/src/Kit.php b/src/Kit.php index aeb359c..36fa174 100644 --- a/src/Kit.php +++ b/src/Kit.php @@ -143,6 +143,9 @@ public static function input( return new Blocks\Input($label, $element, $optional, $hint, $dispatchAction, $blockId); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#rich_text + */ public static function richText( Collections\RichTextSubCollection|array $elements = [], ?string $blockId = null, @@ -176,6 +179,9 @@ public static function video( #endregion #region Elements + /** + * @see https://api.slack.com/reference/block-kit/blocks#broadcast-element-type + */ public static function broadcast( Elements\RichTexts\Range|string|null $range = null, ): Elements\RichTexts\Broadcast { @@ -194,6 +200,9 @@ public static function button( return new Elements\Button($actionId, $text, $value, $style, $url, $confirm, $accessibilityLabel); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#channel-element-type + */ public static function channel( ?string $channelId = null, ?Parts\MentionStyle $style = null, @@ -226,6 +235,9 @@ public static function checkboxes( return new Elements\Checkboxes($actionId, $options, $initialOptions, $confirm, $focusOnLoad); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#color-element-type + */ public static function color(?string $value = null): Elements\RichTexts\Color { return new Elements\RichTexts\Color($value); @@ -244,6 +256,9 @@ public static function conversationSelectMenu( return new Elements\Selects\ConversationSelectMenu($actionId, $placeholder, $initialConversation, $responseUrlEnabled, $defaultToCurrentConversation, $filter, $confirm, $focusOnLoad); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#date-element-type + */ public static function date( ?int $timestamp = null, ?string $format = null, @@ -263,6 +278,9 @@ public static function datePicker( return new Elements\DatePicker($actionId, $initialDate, $placeholder, $confirm, $focusOnLoad); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#emoji-element-type + */ public static function emoji(?string $name = null, ?string $unicode = null): Elements\RichTexts\Emoji { return new Elements\RichTexts\Emoji($name, $unicode); @@ -284,6 +302,9 @@ public static function image(?string $imageUrl = null, ?string $altText = null): return new Elements\Image($imageUrl, $altText); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#link-element-type + */ public static function link( ?string $url = null, ?string $text = null, @@ -422,6 +443,8 @@ public static function radioButtons( /** * @param Collections\RichTextSectionCollection|array $elements + * + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_list */ public static function richTextList( Collections\RichTextSectionCollection|array $elements = [], @@ -435,6 +458,8 @@ public static function richTextList( /** * @param Collections\TextCollection|array $elements + * + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_preformatted */ public static function richTextPreformatted( Collections\TextCollection|array $elements = [], @@ -445,6 +470,8 @@ public static function richTextPreformatted( /** * @param Collections\RichTextCollection|array $elements + * + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_quote */ public static function richTextQuote( Collections\RichTextCollection|array $elements = [], @@ -455,6 +482,8 @@ public static function richTextQuote( /** * @param Collections\RichTextCollection|array $elements + * + * @see https://api.slack.com/reference/block-kit/blocks#rich_text_section */ public static function richTextSection( Collections\RichTextCollection|array $elements = [], @@ -478,6 +507,9 @@ public static function staticSelectMenu( return new Elements\Selects\StaticSelectMenu($actionId, $placeholder, $options, $optionGroups, $initialOption, $confirm, $focusOnLoad); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#text-element-type + */ public static function text(?string $text = null, ?Parts\Style $style = null): Elements\RichTexts\Text { return new Elements\RichTexts\Text($text, $style); @@ -493,11 +525,17 @@ public static function timePicker( return new Elements\TimePicker($actionId, $initialTime, $placeholder, $confirm, $focusOnLoad); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#user-element-type + */ public static function user(?string $userId = null, ?Parts\MentionStyle $style = null): Elements\RichTexts\User { return new Elements\RichTexts\User($userId, $style); } + /** + * @see https://api.slack.com/reference/block-kit/blocks#user-group-element-type + */ public static function usergroup( ?string $usergroupId = null, ?Parts\MentionStyle $style = null, From af67ed4b35065f0cba0faa4acc1b479dec541852 Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Thu, 26 Dec 2024 15:38:33 +0100 Subject: [PATCH 3/3] feat(date): validate format template strings --- src/Elements/RichTexts/Date.php | 3 +- src/Validation/ValidDateFormat.php | 56 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/Validation/ValidDateFormat.php diff --git a/src/Elements/RichTexts/Date.php b/src/Elements/RichTexts/Date.php index f95cf7e..6e9051c 100644 --- a/src/Elements/RichTexts/Date.php +++ b/src/Elements/RichTexts/Date.php @@ -6,6 +6,7 @@ use SlackPhp\BlockKit\Property; use SlackPhp\BlockKit\Validation\RequiresAllOf; +use SlackPhp\BlockKit\Validation\ValidDateFormat; use SlackPhp\BlockKit\Validation\ValidString; use SlackPhp\BlockKit\Validation\ValidUrl; @@ -18,7 +19,7 @@ class Date extends RichTextElement #[Property] public ?int $timestamp; - #[Property, ValidString] + #[Property, ValidDateFormat] public ?string $format; #[Property, ValidUrl] diff --git a/src/Validation/ValidDateFormat.php b/src/Validation/ValidDateFormat.php new file mode 100644 index 0000000..5cc85e0 --- /dev/null +++ b/src/Validation/ValidDateFormat.php @@ -0,0 +1,56 @@ +type->value, $templates], + ); + } + + if (preg_match($containsTemplateRegex, $format) !== 1) { + throw new ValidationException( + 'The "%s" field of a valid "%s" component must at least contain one valid template string (%s)', + [$field, $component->type->value, $templates], + ); + } + } +}