Skip to content

Commit ff33581

Browse files
authored
Add video block (#61)
* style: alphabetically sort and indenting * feat: add video block * chore: remove unused imports * chore: reorder named arguments * feat: add valid url property rule
1 parent 04bddbc commit ff33581

7 files changed

Lines changed: 254 additions & 38 deletions

File tree

src/Blocks/Video.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SlackPhp\BlockKit\Blocks;
6+
7+
use SlackPhp\BlockKit\Parts\PlainText;
8+
use SlackPhp\BlockKit\Property;
9+
use SlackPhp\BlockKit\Validation\RequiresAllOf;
10+
use SlackPhp\BlockKit\Validation\ValidString;
11+
use SlackPhp\BlockKit\Validation\ValidUrl;
12+
13+
#[RequiresAllOf('alt_text', 'title', 'thumbnail_url', 'video_url')]
14+
class Video extends Block
15+
{
16+
#[Property, ValidString(200)]
17+
public ?PlainText $title;
18+
19+
#[Property('video_url'), ValidUrl]
20+
public ?string $videoUrl;
21+
22+
#[Property('thumbnail_url'), ValidUrl]
23+
public ?string $thumbnailUrl;
24+
25+
#[Property('alt_text'), ValidString]
26+
public ?string $altText;
27+
28+
#[Property, ValidString(200)]
29+
public ?PlainText $description;
30+
31+
#[Property('author_name'), ValidString(50)]
32+
public ?string $authorName;
33+
34+
#[Property('title_url'), ValidUrl]
35+
public ?string $titleUrl;
36+
37+
#[Property('provider_name'), ValidString]
38+
public ?string $providerName;
39+
40+
#[Property('provider_icon_url'), ValidUrl]
41+
public ?string $providerIconUrl;
42+
43+
public function __construct(
44+
PlainText|string|null $title = null,
45+
?string $videoUrl = null,
46+
?string $thumbnailUrl = null,
47+
?string $altText = null,
48+
PlainText|string|null $description = null,
49+
?string $authorName = null,
50+
?string $titleUrl = null,
51+
?string $providerName = null,
52+
?string $providerIconUrl = null,
53+
?string $blockId = null,
54+
) {
55+
parent::__construct($blockId);
56+
$this->title($title);
57+
$this->videoUrl($videoUrl);
58+
$this->thumbnailUrl($thumbnailUrl);
59+
$this->altText($altText);
60+
$this->description($description);
61+
$this->authorName($authorName);
62+
$this->titleUrl($titleUrl);
63+
$this->providerName($providerName);
64+
$this->providerIconUrl($providerIconUrl);
65+
}
66+
67+
public function title(PlainText|string|null $title): self
68+
{
69+
$this->title = PlainText::wrap($title);
70+
71+
return $this;
72+
}
73+
74+
public function videoUrl(?string $videoUrl): self
75+
{
76+
$this->videoUrl = $videoUrl;
77+
78+
return $this;
79+
}
80+
81+
public function thumbnailUrl(?string $thumbnailUrl): self
82+
{
83+
$this->thumbnailUrl = $thumbnailUrl;
84+
85+
return $this;
86+
}
87+
88+
public function altText(?string $altText): self
89+
{
90+
$this->altText = $altText;
91+
92+
return $this;
93+
}
94+
95+
public function description(PlainText|string|null $description): self
96+
{
97+
$this->description = PlainText::wrap($description);
98+
99+
return $this;
100+
}
101+
102+
public function authorName(?string $authorName): self
103+
{
104+
$this->authorName = $authorName;
105+
106+
return $this;
107+
}
108+
109+
public function titleUrl(?string $titleUrl): self
110+
{
111+
$this->titleUrl = $titleUrl;
112+
113+
return $this;
114+
}
115+
116+
public function providerName(?string $providerName): self
117+
{
118+
$this->providerName = $providerName;
119+
120+
return $this;
121+
}
122+
123+
public function providerIconUrl(?string $providerIconUrl): self
124+
{
125+
$this->providerIconUrl = $providerIconUrl;
126+
127+
return $this;
128+
}
129+
}

src/Kit.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44

55
namespace SlackPhp\BlockKit;
66

7-
use SlackPhp\BlockKit\Collections;
8-
use SlackPhp\BlockKit\Elements;
9-
use SlackPhp\BlockKit\Parts;
10-
use SlackPhp\BlockKit\Surfaces;
11-
127
/**
138
* Kit acts as a static façade to the whole block kit library.
149
*/
@@ -156,6 +151,21 @@ public static function section(
156151
): Blocks\Section {
157152
return new Blocks\Section($text, $fields, $accessory, $blockId);
158153
}
154+
155+
public static function video(
156+
Parts\PlainText|string|null $title = null,
157+
?string $videoUrl = null,
158+
?string $thumbnailUrl = null,
159+
?string $altText = null,
160+
Parts\PlainText|string|null $description = null,
161+
?string $authorName = null,
162+
?string $titleUrl = null,
163+
?string $providerName = null,
164+
?string $providerIconUrl = null,
165+
?string $blockId = null,
166+
): Blocks\Video {
167+
return new Blocks\Video($title, $videoUrl, $thumbnailUrl, $altText, $description, $authorName, $titleUrl, $providerName, $providerIconUrl, $blockId);
168+
}
159169
#endregion
160170

161171
#region Elements

src/Type.php

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ enum Type: string
1515
case ATTACHMENT = 'attachment';
1616
case MESSAGE = 'message';
1717
case MODAL = 'modal';
18-
case WORKFLOW_STEP = 'workflow_step';
1918
case OPTIONS_RESULT = 'options_result';
19+
case WORKFLOW_STEP = 'workflow_step';
2020

2121
// Blocks
2222
case ACTIONS = 'actions';
@@ -27,6 +27,7 @@ enum Type: string
2727
case HEADER = 'header';
2828
case INPUT = 'input';
2929
case SECTION = 'section';
30+
case VIDEO = 'video';
3031

3132
// Elements
3233
case BUTTON = 'button';
@@ -38,16 +39,16 @@ enum Type: string
3839
case MULTI_SELECT_EXTERNAL = 'multi_external_select';
3940
case MULTI_SELECT_STATIC = 'multi_static_select';
4041
case MULTI_SELECT_USERS = 'multi_users_select';
42+
case NUMBER_INPUT = 'number_input';
4143
case OVERFLOW_MENU = 'overflow';
44+
case PLAIN_TEXT_INPUT = 'plain_text_input';
4245
case RADIO_BUTTONS = 'radio_buttons';
4346
case SELECT_CHANNELS = 'channels_select';
4447
case SELECT_CONVERSATIONS = 'conversations_select';
4548
case SELECT_EXTERNAL = 'external_select';
4649
case SELECT_STATIC = 'static_select';
4750
case SELECT_USERS = 'users_select';
48-
case PLAIN_TEXT_INPUT = 'plain_text_input';
4951
case TIMEPICKER = 'timepicker';
50-
case NUMBER_INPUT = 'number_input';
5152

5253
// Parts (aka Composition Objects)
5354
case CONFIRM = 'confirm';
@@ -66,8 +67,8 @@ enum Type: string
6667
Surfaces\Attachment::class => self::ATTACHMENT,
6768
Surfaces\Message::class => self::MESSAGE,
6869
Surfaces\Modal::class => self::MODAL,
69-
Surfaces\WorkflowStep::class => self::WORKFLOW_STEP,
7070
Surfaces\OptionsResult::class => self::OPTIONS_RESULT,
71+
Surfaces\WorkflowStep::class => self::WORKFLOW_STEP,
7172

7273
// Blocks
7374
Blocks\Actions::class => self::ACTIONS,
@@ -78,33 +79,34 @@ enum Type: string
7879
Blocks\Header::class => self::HEADER,
7980
Blocks\Input::class => self::INPUT,
8081
Blocks\Section::class => self::SECTION,
82+
Blocks\Video::class => self::VIDEO,
8183

8284
// Virtual Blocks
8385
Virtual\CodeBlock::class => self::SECTION,
8486
Virtual\TwoColumnTable::class => self::SECTION,
8587

8688
// Elements
87-
Elements\Button::class => self::BUTTON,
88-
Elements\Checkboxes::class => self::CHECKBOXES,
89-
Elements\DatePicker::class => self::DATEPICKER,
90-
Elements\Image::class => self::IMAGE,
91-
Elements\RadioButtons::class => self::RADIO_BUTTONS,
92-
Elements\PlainTextInput::class => self::PLAIN_TEXT_INPUT,
93-
Elements\TimePicker::class => self::TIMEPICKER,
94-
Elements\NumberInput::class => self::NUMBER_INPUT,
89+
Elements\Button::class => self::BUTTON,
90+
Elements\Checkboxes::class => self::CHECKBOXES,
91+
Elements\DatePicker::class => self::DATEPICKER,
92+
Elements\Image::class => self::IMAGE,
93+
Elements\NumberInput::class => self::NUMBER_INPUT,
94+
Elements\OverflowMenu::class => self::OVERFLOW_MENU,
95+
Elements\PlainTextInput::class => self::PLAIN_TEXT_INPUT,
96+
Elements\RadioButtons::class => self::RADIO_BUTTONS,
97+
Elements\TimePicker::class => self::TIMEPICKER,
9598

9699
// Menus
97-
Elements\OverflowMenu::class => self::OVERFLOW_MENU,
98-
Selects\MultiChannelSelectMenu::class => self::MULTI_SELECT_CHANNELS,
99-
Selects\MultiConversationSelectMenu::class => self::MULTI_SELECT_CONVERSATIONS,
100-
Selects\MultiExternalSelectMenu::class => self::MULTI_SELECT_EXTERNAL,
101-
Selects\MultiStaticSelectMenu::class => self::MULTI_SELECT_STATIC,
102-
Selects\MultiUserSelectMenu::class => self::MULTI_SELECT_USERS,
103-
Selects\ChannelSelectMenu::class => self::SELECT_CHANNELS,
104-
Selects\ConversationSelectMenu::class => self::SELECT_CONVERSATIONS,
105-
Selects\ExternalSelectMenu::class => self::SELECT_EXTERNAL,
106-
Selects\StaticSelectMenu::class => self::SELECT_STATIC,
107-
Selects\UserSelectMenu::class => self::SELECT_USERS,
100+
Selects\ChannelSelectMenu::class => self::SELECT_CHANNELS,
101+
Selects\ConversationSelectMenu::class => self::SELECT_CONVERSATIONS,
102+
Selects\ExternalSelectMenu::class => self::SELECT_EXTERNAL,
103+
Selects\MultiChannelSelectMenu::class => self::MULTI_SELECT_CHANNELS,
104+
Selects\MultiConversationSelectMenu::class => self::MULTI_SELECT_CONVERSATIONS,
105+
Selects\MultiExternalSelectMenu::class => self::MULTI_SELECT_EXTERNAL,
106+
Selects\MultiStaticSelectMenu::class => self::MULTI_SELECT_STATIC,
107+
Selects\MultiUserSelectMenu::class => self::MULTI_SELECT_USERS,
108+
Selects\StaticSelectMenu::class => self::SELECT_STATIC,
109+
Selects\UserSelectMenu::class => self::SELECT_USERS,
108110

109111
// Parts (aka Composition Objects)
110112
Parts\Confirm::class => self::CONFIRM,

src/Validation/ValidUrl.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SlackPhp\BlockKit\Validation;
6+
7+
use Attribute;
8+
use SlackPhp\BlockKit\Component;
9+
10+
#[Attribute(Attribute::TARGET_PROPERTY)]
11+
class ValidUrl implements PropertyRule
12+
{
13+
public function check(Component $component, string $field, mixed $value): void
14+
{
15+
if ($value === null) {
16+
return;
17+
}
18+
19+
if (! \is_string($value) || filter_var($value, FILTER_VALIDATE_URL) === false) {
20+
throw new ValidationException(
21+
'The "%s" field of a valid "%s" component must be a valid URL',
22+
[$field, $component->type->value],
23+
);
24+
}
25+
}
26+
}

tests/Functional/BlockTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace SlackPhp\BlockKit\Tests\Functional;
4+
5+
use SlackPhp\BlockKit\Blocks\Block;
6+
use SlackPhp\BlockKit\Blocks\Video;
7+
8+
class BlockTest extends TestCase
9+
{
10+
/**
11+
* @param class-string<Block> $class
12+
* @dataProvider providesJsonFiles
13+
*/
14+
public function testMatchesJsonFromReferenceDocumentation(string $file, string $class): void
15+
{
16+
$json = $this->loadAssetJson($file);
17+
18+
$hydrate = [$class, 'fromJson'](...);
19+
$block = $hydrate($json);
20+
$block->validate();
21+
$newJson = $block->toJson();
22+
23+
$this->assertJsonStringEqualsJsonString($json, $newJson);
24+
}
25+
26+
public static function providesJsonFiles(): array
27+
{
28+
return [
29+
['blocks/video', Video::class],
30+
];
31+
}
32+
}

tests/Functional/CreateTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ class CreateTest extends TestCase
2020
public function testCreatedComponentsMatchExpectedOutputJson(): void
2121
{
2222
$modal = Kit::modal(
23-
title: 'My App',
24-
submit: 'Submit',
25-
close: 'Cancel',
2623
blocks: [
2724
Kit::input(
2825
label: 'Choose a letter',
@@ -48,6 +45,9 @@ public function testCreatedComponentsMatchExpectedOutputJson(): void
4845
),
4946
)
5047
],
48+
title: 'My App',
49+
submit: 'Submit',
50+
close: 'Cancel',
5151
);
5252

5353
$modal->validate();
@@ -66,21 +66,21 @@ public function testCreatedVirtualComponentsMatchExpectedOutputJson(): void
6666
$message = Kit::message(
6767
blocks: [
6868
Kit::twoColumnTable(
69-
blockId: 'foo',
70-
cols: ['left', 'right'],
7169
rows: [
7270
['a', 'b'],
7371
['c', 'd'],
7472
],
73+
cols: ['left', 'right'],
7574
borders: true,
75+
blockId: 'foo',
7676
),
7777
Kit::codeBlock(
78-
blockId: 'bar',
79-
caption: 'my-code.txt',
8078
code: <<<CODE
8179
This is
8280
my code
8381
CODE,
82+
caption: 'my-code.txt',
83+
blockId: 'bar',
8484
),
8585
Kit::codeBlock(
8686
code: 'Code block without blockId'
@@ -97,13 +97,13 @@ public function testCreatedVirtualComponentsMatchExpectedOutputJson(): void
9797
public function testCreateModalWithPrivateMetadata(): void
9898
{
9999
$modal = Kit::modal(
100+
blocks: [
101+
Kit::section('Hello, world!'),
102+
],
100103
title: 'My App',
101104
privateMetadata: [
102105
'foo' => 'bar',
103106
],
104-
blocks: [
105-
Kit::section('Hello, world!'),
106-
],
107107
);
108108

109109
$modal->validate();

0 commit comments

Comments
 (0)