Skip to content

Commit

Permalink
add select option
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobiáš Štancel committed Jan 31, 2025
1 parent aa5a712 commit a0fc7f6
Show file tree
Hide file tree
Showing 5 changed files with 390 additions and 0 deletions.
110 changes: 110 additions & 0 deletions src/Slack/BlockKit/Elements/Selects/SelectElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace Illuminate\Notifications\Slack\BlockKit\Elements\Selects;

use Illuminate\Notifications\Slack\BlockKit\Composites\PlainTextOnlyTextObject;
use Illuminate\Notifications\Slack\Contracts\AccessoryContract;
use InvalidArgumentException;

/**
* Abstract class representing a base structure for select elements.
*
* The class provides functionality for defining interaction identifiers,
* placeholders, and focus behavior. It also enforces subclasses to
* implement additional fields specific to their select type.
*/
abstract class SelectElement implements AccessoryContract
{
/**
* An identifier for this action.
*
* You can use this when you receive an interaction payload to identify the source of the action.
*
* Should be unique among all other action_ids in the containing block.
*
* Maximum length for this field is 255 characters.
*/
protected string $actionId;

/**
* A text object that defines the select's text.
*
* Can only be of type: plain_text. Text may truncate with ~30 characters.
*
* Maximum length for the text in this field is 75 characters.
*/
protected ?PlainTextOnlyTextObject $placeholder = null;

/**
* Indicates whether the element should automatically gain focus when the view loads.
*
* When set to `true`, this element will automatically receive focus in the UI.
* Useful for prioritizing user interaction.
*/
protected ?bool $focusOnLoad = null;

/**
* Set the action ID for the select.
*/
public function id(string $id): self
{
if (strlen($id) > 255) {
throw new InvalidArgumentException('Maximum length for the action_id field is 255 characters.');
}

$this->actionId = $id;

return $this;
}

/**
* Set the placeholder text.
*/
public function placeholder(string $text): self
{
$this->placeholder = new PlainTextOnlyTextObject($text);

return $this;
}

/**
* Set whether the element should automatically gain focus when the view loads.
*/
public function focusOnLoad(bool $focusOnLoad = true): self
{
$this->focusOnLoad = $focusOnLoad;

return $this;
}

/**
* Get the instance as an array.
*/
public function toArray(): array
{
$allFields = array_merge($this->extensionFields(), [
'action_id' => $this->actionId,
'placeholder' => $this->placeholder?->toArray(),
'focus_on_load' => $this->focusOnLoad,
]);

return array_filter(
$allFields,
static fn ($value): bool => $value !== null,
);
}

/**
* Get additional fields specific to the child class as an associative array.
*
* This method should be implemented in subclasses to provide additional
* fields required by the specific select element type.
*
* @return array The additional fields for the select element.
*/
abstract protected function extensionFields(): array;


}
55 changes: 55 additions & 0 deletions src/Slack/BlockKit/Elements/Selects/SelectOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Illuminate\Notifications\Slack\BlockKit\Elements\Selects;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Notifications\Slack\BlockKit\Composites\TextObject;
use Illuminate\Support\Str;

final class SelectOption implements Arrayable
{
/**
* Text of the options.
*/
private TextObject $text;

/**
* Value of the option.
*/
private string $value;

public function __construct(string $text, $value)
{
$this->text($text);
$this->value($value);
}

/**
* Sets the select text value.
*/
private function text(string $text): void
{
$this->text = new TextObject($text, 75);
}

/**
* Sets the select value.
*/
private function value($value): void
{
$value = Str::lower($value);
$value = preg_replace('/[^a-z0-9_\-.]/', '', $value);

$this->value = $value;
}

public function toArray(): array
{
return [
'text' => $this->text->toArray(),
'value' => $this->value,
];
}
}
66 changes: 66 additions & 0 deletions src/Slack/BlockKit/Elements/Selects/StaticSelectElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Illuminate\Notifications\Slack\BlockKit\Elements\Selects;

use Closure;
use Illuminate\Notifications\Slack\BlockKit\Elements\Traits\DefaultIdTrait;
use InvalidArgumentException;

final class StaticSelectElement extends SelectElement
{
use DefaultIdTrait;

/**
* @var array<string, SelectOption> An array mapping values keys to SelectOption objects
*/
private array $options = [];

/**
* @var SelectOption|null The initially selected option, or null if none is set
*/
private ?SelectOption $initialOption = null;

public function __construct()
{
$this->id($this->resolveDefaultId('static_select_'));
}

/**
* Adds an option to the static select element.
*/
public function addOption(string $text, string $value): self
{
$this->options[$value] = new SelectOption($text, $value);

return $this;
}

/**
* Sets the default selected option for the static select element.
*/
public function initialOption(string $value): self
{
$option = $this->options[$value] ?? null;
if ($option === null) {
throw new InvalidArgumentException("Unknown option value: $value.");
}

$this->initialOption = $option;

return $this;
}

protected function extensionFields(): array
{
$options = array_values($this->options);
$options = array_map(fn (SelectOption $option) => $option->toArray(), $options);

return [
'type' => 'static_select',
'options' => $options,
'initial_option' => $this->initialOption?->toArray(),
];
}
}
10 changes: 10 additions & 0 deletions src/Slack/Contracts/AccessoryContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Illuminate\Notifications\Slack\Contracts;

interface AccessoryContract extends ElementContract
{
//
}
149 changes: 149 additions & 0 deletions tests/Slack/Unit/Elements/Selects/StaticSelectElementTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

declare(strict_types=1);

namespace Illuminate\Tests\Notifications\Slack\Unit\Elements\Selects;

use Illuminate\Notifications\Slack\BlockKit\Elements\Selects\StaticSelectElement;
use Illuminate\Tests\Notifications\Slack\TestCase;

final class StaticSelectElementTest extends TestCase
{
/** @test */
public function test_it_can_add_initial_option(): void
{
$select = new StaticSelectElement();
$select->id('initial_option_select');
$select->addOption('Option A', 'option_a');
$select->initialOption('option_a');

$this->assertEquals([
'type' => 'static_select',
'action_id' => 'initial_option_select',
'options' => [
[
'text' => [
'type' => 'plain_text',
'text' => 'Option A',
],
'value' => 'option_a',
],
],
'initial_option' => [
'text' => [
'type' => 'plain_text',
'text' => 'Option A',
],
'value' => 'option_a',
],
], $select->toArray());
}

/** @test */
public function test_it_can_enable_focus_on_load(): void
{
$select = new StaticSelectElement();
$select->id('enable_focus');
$select->focusOnLoad(true);

$this->assertEquals([
'type' => 'static_select',
'action_id' => 'enable_focus',
'focus_on_load' => true,
'options' => [],
], $select->toArray());
}

/** @test */
public function test_it_rejects_invalid_placeholder_text(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Text must be at least 1 character(s) long.');

$select = new StaticSelectElement();
$select->id('invalid_placeholder');
$select->placeholder('');
}

/** @test */
public function test_it_can_add_multiple_options(): void
{
$select = new StaticSelectElement();
$select->id('multi_select');
$select->addOption('Option A', 'option_a');
$select->addOption('Option B', 'option_b');
$select->addOption('Option C', 'option_c');

$this->assertEquals([
'type' => 'static_select',
'action_id' => 'multi_select',
'options' => [
[
'text' => [
'type' => 'plain_text',
'text' => 'Option A',
],
'value' => 'option_a',
],
[
'text' => [
'type' => 'plain_text',
'text' => 'Option B',
],
'value' => 'option_b',
],
[
'text' => [
'type' => 'plain_text',
'text' => 'Option C',
],
'value' => 'option_c',
],
],
], $select->toArray());
}

/** @test */
public function test_it_can_set_placeholder(): void
{
$select = new StaticSelectElement();
$select->id('placeholder_select');
$select->placeholder('Choose an option');

$this->assertEquals([
'type' => 'static_select',
'action_id' => 'placeholder_select',
'placeholder' => [
'type' => 'plain_text',
'text' => 'Choose an option',
],
'options' => [],
], $select->toArray());
}

/** @test */
public function test_it_can_disable_focus_on_load(): void
{
$select = new StaticSelectElement();
$select->id('disable_focus');
$select->focusOnLoad(false);

$this->assertEquals([
'type' => 'static_select',
'action_id' => 'disable_focus',
'focus_on_load' => false,
'options' => [],
], $select->toArray());
}

/** @test */
public function test_it_rejects_initial_option_when_no_options_available(): void
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unknown option value: non_existent_option.');

$select = new StaticSelectElement();
$select->id('no_options');
$select->initialOption('non_existent_option');
}
}

0 comments on commit a0fc7f6

Please sign in to comment.