From 0163fa53b4dd72ec917eafb6601f2862e4f8516d Mon Sep 17 00:00:00 2001 From: myrmidex Date: Thu, 13 Aug 2026 18:44:59 +0200 Subject: [PATCH] 137 - Add an edit option to channel cards --- app/Livewire/Channels.php | 50 ++++++ app/Models/PlatformChannel.php | 4 +- phpstan-baseline.neon | 6 - resources/views/livewire/channels.blade.php | 109 +++++++++++-- tests/Feature/Livewire/ChannelsTest.php | 162 ++++++++++++++++++++ 5 files changed, 315 insertions(+), 16 deletions(-) diff --git a/app/Livewire/Channels.php b/app/Livewire/Channels.php index ebb7099d..795fc8b0 100644 --- a/app/Livewire/Channels.php +++ b/app/Livewire/Channels.php @@ -34,6 +34,14 @@ class Channels extends Component public string $newDescription = ''; + public ?int $editingChannelId = null; + + public string $editDisplayName = ''; + + public ?int $editLanguageId = null; + + public string $editDescription = ''; + public function toggle(int $channelId): void { $channel = PlatformChannel::findOrFail($channelId); @@ -127,6 +135,45 @@ public function createChannel(CreateChannelAction $action): void $this->closeCreateModal(); } + public function openEditModal(int $channelId): void + { + $channel = PlatformChannel::findOrFail($channelId); + + $this->resetErrorBag(); + $this->editingChannelId = $channelId; + $this->editDisplayName = $channel->display_name; + $this->editLanguageId = $channel->language_id; + $this->editDescription = $channel->description ?? ''; + } + + public function closeEditModal(): void + { + $this->editingChannelId = null; + } + + // The community pairing (name, channel_id, platform_instance_id) is deliberately immutable: + // it is the channel's remote identity, unique per instance, and re-pointing it would change + // the meaning of every route already attached. + public function updateChannel(): void + { + if ($this->editingChannelId === null) { + return; + } + + $this->validate([ + 'editDisplayName' => 'required|string|max:255', + 'editLanguageId' => 'nullable|integer|exists:languages,id', + ]); + + PlatformChannel::findOrFail($this->editingChannelId)->update([ + 'display_name' => $this->editDisplayName, + 'language_id' => $this->editLanguageId, + 'description' => $this->editDescription !== '' ? $this->editDescription : null, + ]); + + $this->closeEditModal(); + } + public function openAccountModal(int $channelId): void { $this->managingChannelId = $channelId; @@ -177,6 +224,9 @@ public function render(): View return view('livewire.channels', [ 'channels' => $channels, 'managingChannel' => $managingChannel, + 'editingChannel' => $this->editingChannelId !== null + ? PlatformChannel::with('platformInstance')->find($this->editingChannelId) + : null, 'availableAccounts' => $availableAccounts, 'platformInstances' => PlatformInstance::where('is_active', true)->orderBy('name')->get(), 'languages' => Language::where('is_active', true)->orderBy('name')->get(), diff --git a/app/Models/PlatformChannel.php b/app/Models/PlatformChannel.php index 12f88fac..7b85455e 100644 --- a/app/Models/PlatformChannel.php +++ b/app/Models/PlatformChannel.php @@ -17,7 +17,9 @@ * @property PlatformInstance $platformInstance * @property int $channel_id * @property string $name - * @property int $language_id + * @property string $display_name + * @property string|null $description + * @property int|null $language_id * @property Language|null $language * @property bool $is_active */ diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f74a15a0..93a19e71 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12,12 +12,6 @@ parameters: count: 1 path: tests/Unit/Actions/CreateChannelActionTest.php - - - message: '#^Call to method PHPUnit\\Framework\\Assert\:\:assertNull\(\) with int will always evaluate to false\.$#' - identifier: method.impossibleType - count: 1 - path: tests/Unit/Actions/CreateChannelActionTest.php - - message: '#^Access to an undefined property App\\Models\\Route\:\:\$id\.$#' identifier: property.notFound diff --git a/resources/views/livewire/channels.blade.php b/resources/views/livewire/channels.blade.php index cecf64f9..a29461ba 100644 --- a/resources/views/livewire/channels.blade.php +++ b/resources/views/livewire/channels.blade.php @@ -35,15 +35,28 @@ class="text-sm text-blue-500 hover:underline"> @endif - +
+ + + +
@if ($channel->description) @@ -259,4 +272,82 @@ class="inline-flex justify-center rounded-md border border-transparent shadow-xs @endif + + + @if ($editingChannel) + +
+
+ + + @error('editDisplayName')

{{ $message }}

@enderror +
+ +
+ + + @error('editLanguageId')

{{ $message }}

@enderror +
+ +
+ + + @error('editDescription')

{{ $message }}

@enderror +
+ +
+
+
+
Community
+
{{ $editingChannel->name }}
+
+
+
Instance
+
{{ $editingChannel->platformInstance->name }}
+
+
+

+ The community and instance identify this channel on the remote platform and cannot be changed. Every route attached to this channel publishes here. Create a new channel to post somewhere else. +

+
+ +
+ + +
+
+
+ @endif diff --git a/tests/Feature/Livewire/ChannelsTest.php b/tests/Feature/Livewire/ChannelsTest.php index db8e876c..f1a1e6df 100644 --- a/tests/Feature/Livewire/ChannelsTest.php +++ b/tests/Feature/Livewire/ChannelsTest.php @@ -248,4 +248,166 @@ public function test_toggle_flips_active_state(): void $this->assertFalse($channel->fresh()->is_active); } + + public function test_channel_cards_show_an_edit_action(): void + { + PlatformChannel::factory()->create(['display_name' => 'Tech Community']); + + Livewire::test(Channels::class) + ->assertSee('Edit Tech Community'); + } + + public function test_open_edit_modal_prefills_the_current_values(): void + { + $language = Language::factory()->create(); + $channel = PlatformChannel::factory()->create([ + 'display_name' => 'Tech Community', + 'description' => 'A place for tech', + 'language_id' => $language->id, + ]); + + Livewire::test(Channels::class) + ->assertSet('editingChannelId', null) + ->call('openEditModal', $channel->id) + ->assertSet('editingChannelId', $channel->id) + ->assertSet('editDisplayName', 'Tech Community') + ->assertSet('editDescription', 'A place for tech') + ->assertSet('editLanguageId', $language->id) + ->assertSee('Edit Channel'); + } + + public function test_open_edit_modal_prefills_a_null_description_as_blank(): void + { + $channel = PlatformChannel::factory()->create(['description' => null]); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->assertSet('editDescription', ''); + } + + public function test_update_channel_persists_the_changes_and_closes_the_modal(): void + { + $language = Language::factory()->create(); + $channel = PlatformChannel::factory()->create([ + 'display_name' => 'Old Name', + 'description' => 'Old description', + 'language_id' => null, + ]); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editDisplayName', 'New Name') + ->set('editDescription', 'New description') + ->set('editLanguageId', $language->id) + ->call('updateChannel') + ->assertSet('editingChannelId', null) + ->assertHasNoErrors(); + + $channel->refresh(); + + $this->assertSame('New Name', $channel->display_name); + $this->assertSame('New description', $channel->description); + $this->assertSame($language->id, $channel->language_id); + } + + public function test_update_channel_requires_a_display_name(): void + { + $channel = PlatformChannel::factory()->create(['display_name' => 'Original']); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editDisplayName', '') + ->call('updateChannel') + ->assertHasErrors(['editDisplayName' => 'required']); + + $this->assertSame('Original', $channel->fresh()->display_name); + } + + public function test_update_channel_rejects_a_display_name_over_the_length_limit(): void + { + $channel = PlatformChannel::factory()->create(['display_name' => 'Original']); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editDisplayName', str_repeat('a', 256)) + ->call('updateChannel') + ->assertHasErrors(['editDisplayName' => 'max']); + + $this->assertSame('Original', $channel->fresh()->display_name); + } + + public function test_update_channel_rejects_an_unknown_language(): void + { + $channel = PlatformChannel::factory()->create(['display_name' => 'Original']); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editLanguageId', 999999) + ->call('updateChannel') + ->assertHasErrors(['editLanguageId' => 'exists']); + } + + public function test_update_channel_allows_clearing_the_language(): void + { + $language = Language::factory()->create(); + $channel = PlatformChannel::factory()->create(['language_id' => $language->id]); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editLanguageId', null) + ->call('updateChannel') + ->assertHasNoErrors(); + + $this->assertNull($channel->fresh()->language_id); + } + + public function test_update_channel_stores_a_blank_description_as_null(): void + { + $channel = PlatformChannel::factory()->create(['description' => 'Has one']); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editDescription', '') + ->call('updateChannel'); + + $this->assertNull($channel->fresh()->description); + } + + public function test_update_channel_leaves_the_community_pairing_untouched(): void + { + $channel = PlatformChannel::factory()->create(['display_name' => 'Original']); + $before = $channel->only(['name', 'channel_id', 'platform_instance_id']); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editDisplayName', 'Renamed') + ->call('updateChannel'); + + $this->assertSame($before, $channel->fresh()->only(['name', 'channel_id', 'platform_instance_id'])); + } + + public function test_close_edit_modal_discards_the_edit(): void + { + $channel = PlatformChannel::factory()->create(['display_name' => 'Original']); + + Livewire::test(Channels::class) + ->call('openEditModal', $channel->id) + ->set('editDisplayName', 'Discarded') + ->call('closeEditModal') + ->assertSet('editingChannelId', null); + + $this->assertSame('Original', $channel->fresh()->display_name); + } + + public function test_update_channel_does_nothing_without_an_open_modal(): void + { + $channel = PlatformChannel::factory()->create(['display_name' => 'Original']); + + Livewire::test(Channels::class) + ->set('editDisplayName', 'Should not apply') + ->call('updateChannel') + ->assertHasNoErrors(); + + $this->assertSame('Original', $channel->fresh()->display_name); + } }