diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 6f111d81..1e9ad16b 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -21,6 +21,13 @@ public function mount(): void $this->applyPreset('today'); } + /** + * Islands whose content depends on the global range; skipped islands never re-render on their own. + * + * @var array + */ + private const RANGE_DEPENDENT_ISLANDS = ['article-statistics']; + public function applyPreset(string $preset): void { try { @@ -29,13 +36,29 @@ public function applyPreset(string $preset): void return; } - $this->from = $range->from->toDateString(); - $this->to = $range->to->toDateString(); + $this->applyRange($range->from->toDateString(), $range->to->toDateString()); + } + + public function applyRange(string $from, string $to): void + { + $this->from = $from; + $this->to = $to; + + $this->validateRange(); + $this->refreshRangeDependentIslands(); + } + + private function refreshRangeDependentIslands(): void + { + foreach (self::RANGE_DEPENDENT_ISLANDS as $island) { + $this->renderIsland(name: $island); + } } /** * @return array */ + #[Computed] public function presets(): array { return DateRange::presets(); @@ -100,6 +123,7 @@ public function updated(string $property): void { if (in_array($property, ['from', 'to'], true)) { $this->validateRange(); + $this->refreshRangeDependentIslands(); } } @@ -127,8 +151,6 @@ private function validateRange(): void public function render(): View { - return view('livewire.dashboard', [ - 'presets' => $this->presets(), - ])->layout('layouts.app'); + return view('livewire.dashboard')->layout('layouts.app'); } } diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index 77597051..42e44b7e 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -2,6 +2,7 @@ + @island('system-overview')

System Overview

@@ -78,6 +79,7 @@
+ @endisland
@@ -86,7 +88,7 @@
- @foreach ($presets as $value => $label) + @foreach ($this->presets as $value => $label)
+ @endisland
diff --git a/tests/Feature/Livewire/DashboardTest.php b/tests/Feature/Livewire/DashboardTest.php index 425bb03f..9b13fa5d 100644 --- a/tests/Feature/Livewire/DashboardTest.php +++ b/tests/Feature/Livewire/DashboardTest.php @@ -6,6 +6,7 @@ use App\Models\Article; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Carbon; +use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; use Tests\TestCase; @@ -119,4 +120,74 @@ public function test_it_renders_the_preset_buttons(): void ->assertSee('This Month') ->assertSee('All Time'); } + + public function test_it_renders_each_panel_in_its_own_island(): void + { + $html = Livewire::test(Dashboard::class)->html(); + + foreach (['system-overview', 'article-statistics'] as $island) { + $this->assertSame( + 1, + substr_count($html, "[if FRAGMENT:type=island|name={$island}|"), + "Expected exactly one [{$island}] island.", + ); + } + } + + /** + * @param Testable $component + */ + private function islandFragments(Testable $component): string + { + /** @var array $fragments */ + $fragments = $component->effects['islandFragments'] ?? []; + + return implode("\n", $fragments); + } + + public function test_it_re_renders_the_article_statistics_island_when_the_range_changes(): void + { + Article::factory()->count(3)->create(['created_at' => Carbon::parse('2026-07-10 12:00:00')]); + + $fragments = $this->islandFragments( + Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') + ); + + $this->assertMatchesRegularExpression('/Articles Fetched.*?>\s*3\s*count(3)->create(['created_at' => Carbon::parse('2026-07-10 12:00:00')]); + + $fragments = $this->islandFragments( + Livewire::test(Dashboard::class)->call('applyRange', '2026-08-01', '2026-08-31') + ); + + $this->assertMatchesRegularExpression('/Articles Fetched.*?>\s*0\s*islandFragments( + Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') + ); + + $this->assertStringContainsString('name=article-statistics|', $fragments); + $this->assertStringNotContainsString('name=system-overview|', $fragments); + $this->assertStringNotContainsString('Active Feeds', $fragments); + } + + public function test_it_re_renders_the_article_statistics_island_when_a_preset_is_applied(): void + { + Carbon::setTestNow('2026-07-15 13:45:00'); + + Article::factory()->count(2)->create(['created_at' => Carbon::parse('2026-07-15 09:00:00')]); + + $fragments = $this->islandFragments( + Livewire::test(Dashboard::class)->call('applyPreset', 'month') + ); + + $this->assertMatchesRegularExpression('/Articles Fetched.*?>\s*2\s*