assertSet('from', '2026-07-01') ->assertSet('to', '2026-07-31'); } public function test_it_recomputes_stats_when_the_range_changes(): void { Article::factory()->count(3)->create(['created_at' => Carbon::parse('2026-07-10 12:00:00')]); Livewire::test(Dashboard::class) ->set('from', '2026-07-01') ->set('to', '2026-07-31') ->assertSee('3') ->set('from', '2026-08-01') ->set('to', '2026-08-31') ->assertSee('0'); } public function test_it_populates_the_range_when_a_preset_is_applied(): void { Carbon::setTestNow('2026-07-15 13:45:00'); Livewire::test(Dashboard::class) ->call('applyPreset', 'month') ->assertSet('from', '2026-07-01') ->assertSet('to', '2026-07-31'); } public function test_it_applies_the_all_preset_as_a_bounded_range(): void { Carbon::setTestNow('2026-07-15 13:45:00'); Livewire::test(Dashboard::class) ->call('applyPreset', 'all') ->assertSet('to', '2026-07-15'); } public function test_it_ignores_an_unknown_preset(): void { Carbon::setTestNow('2026-07-15 13:45:00'); Livewire::test(Dashboard::class) ->call('applyPreset', 'fortnight') ->assertSet('from', '2026-07-01') ->assertSet('to', '2026-07-31') ->assertHasNoErrors(); } public function test_it_reports_an_inverted_range_against_the_end_date(): void { Carbon::setTestNow('2026-07-15 13:45:00'); Livewire::test(Dashboard::class) ->set('from', '2026-07-31') ->set('to', '2026-07-01') ->assertHasErrors('to'); } public function test_it_blames_the_start_date_when_the_start_date_is_malformed(): void { Carbon::setTestNow('2026-07-15 13:45:00'); Livewire::test(Dashboard::class) ->set('from', 'not-a-date') ->assertHasErrors('from') ->assertHasNoErrors('to'); } public function test_it_blames_the_end_date_when_the_end_date_is_malformed(): void { Carbon::setTestNow('2026-07-15 13:45:00'); Livewire::test(Dashboard::class) ->set('to', 'not-a-date') ->assertHasErrors('to') ->assertHasNoErrors('from'); } public function test_it_reports_zero_stats_for_an_invalid_range(): void { Article::factory()->create(['created_at' => Carbon::parse('2026-07-10 12:00:00')]); Livewire::test(Dashboard::class) ->set('from', '2026-07-01') ->set('to', 'not-a-date') ->assertSet('rangeIsValid', false); } public function test_it_marks_a_well_formed_range_as_valid(): void { Livewire::test(Dashboard::class) ->set('from', '2026-07-01') ->set('to', '2026-07-31') ->assertSet('rangeIsValid', true); } public function test_it_renders_the_preset_buttons(): void { Livewire::test(Dashboard::class) ->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_renders_the_articles_per_feed_breakdown_on_mount(): void { Carbon::setTestNow('2026-07-15 13:45:00'); $feed = Feed::factory()->create(['name' => 'Example Feed']); Article::factory()->count(2)->create([ 'feed_id' => $feed->id, 'created_at' => Carbon::parse('2026-07-15 09:00:00'), ]); Livewire::test(Dashboard::class) ->assertSee('Example Feed') ->assertSee('Articles per Feed'); } public function test_it_counts_articles_per_feed_for_the_selected_range(): void { $feed = Feed::factory()->create(['name' => 'Example Feed']); Article::factory()->count(2)->create([ 'feed_id' => $feed->id, '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('/Example Feed.*?>\s*2\s*/s', $fragments); } public function test_it_refreshes_the_articles_per_feed_island_when_the_range_changes(): void { Feed::factory()->create(['name' => 'Example Feed']); $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('name=articles-per-feed|', $fragments); $this->assertStringContainsString('Example Feed', $fragments); } public function test_it_renders_the_publications_per_channel_breakdown_on_mount(): void { Carbon::setTestNow('2026-07-15 13:45:00'); PlatformChannel::factory()->create(['display_name' => 'Example Channel']); Livewire::test(Dashboard::class) ->assertSee('Publications per Channel') ->assertSee('Example Channel'); } public function test_it_refreshes_the_publications_per_channel_island_when_the_range_changes(): void { PlatformChannel::factory()->create(['display_name' => 'Example Channel']); $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('name=publications-per-channel|', $fragments); $this->assertStringContainsString('Example Channel', $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*create(['created_at' => Carbon::parse('2026-07-15 09:00:00')]); Livewire::test(Dashboard::class) ->assertSee('Fetched vs Published') ->assertSee('trendChart(', false) ->assertSee('x-ref="canvas"', false); } public function test_it_renders_the_trend_chart_in_its_own_island(): void { $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('name=articles-trend|', $fragments); } public function test_it_refreshes_the_trend_island_when_the_range_changes(): void { Article::factory()->count(2)->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->assertStringContainsString('name=articles-trend|', $fragments); $this->assertStringContainsString('2026-07-10', $fragments); $this->assertStringContainsString('Fetched', $fragments); $this->assertStringContainsString('Published', $fragments); } public function test_it_reports_a_too_wide_range_instead_of_charting_it(): void { Carbon::setTestNow('2026-07-15 13:45:00'); $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyPreset', 'all') ); $this->assertStringContainsString('too wide to chart by day', $fragments); $this->assertStringNotContainsString('x-ref="canvas"', $fragments); } public function test_it_reports_an_empty_range_separately_from_a_too_wide_one(): void { $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('No articles or publications in this range.', $fragments); $this->assertStringNotContainsString('too wide to chart by day', $fragments); } public function test_it_keys_the_chart_on_its_data_so_a_range_change_replaces_it(): void { Article::factory()->create(['created_at' => Carbon::parse('2026-07-10 12:00:00')]); Article::factory()->create(['created_at' => Carbon::parse('2026-08-10 12:00:00')]); $july = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $august = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-08-01', '2026-08-31') ); if (preg_match('/wire:key="(chart-[a-f0-9]+)"/', $july, $julyKey) !== 1) { $this->fail('July fragments contained no chart key.'); } if (preg_match('/wire:key="(chart-[a-f0-9]+)"/', $august, $augustKey) !== 1) { $this->fail('August fragments contained no chart key.'); } $this->assertNotSame($julyKey[1], $augustKey[1]); } public function test_it_keys_each_chart_separately_when_their_payloads_match(): void { $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); preg_match_all('/wire:key="(chart-[a-f0-9]+)"/', $fragments, $keys); $this->assertSame($keys[1], array_unique($keys[1])); } public function test_it_renders_the_approval_rate_chart_on_mount(): void { Carbon::setTestNow('2026-07-15 13:45:00'); RouteArticle::factory()->create([ 'approval_status' => ApprovalStatusEnum::APPROVED, 'decided_at' => Carbon::parse('2026-07-15 09:00:00'), ]); Livewire::test(Dashboard::class) ->assertSee('Approval Rate') ->assertSee('trendChart(', false) ->assertSee('x-ref="canvas"', false); } public function test_it_refreshes_the_approval_rate_island_when_the_range_changes(): void { RouteArticle::factory()->create([ 'approval_status' => ApprovalStatusEnum::APPROVED, 'decided_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->assertStringContainsString('name=approval-rate|', $fragments); $this->assertStringContainsString('Approval Rate', $fragments); $this->assertStringContainsString('2026-07-10', $fragments); } public function test_it_charts_a_day_where_everything_was_rejected(): void { RouteArticle::factory()->create([ 'approval_status' => ApprovalStatusEnum::REJECTED, 'decided_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->assertStringNotContainsString('No approval decisions in this range.', $fragments); } public function test_it_reports_a_range_with_no_approval_decisions(): void { $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('No approval decisions in this range.', $fragments); } public function test_it_renders_an_empty_trend_for_an_invalid_range(): void { Livewire::test(Dashboard::class) ->set('from', 'not-a-date') ->assertSee('Fetched vs Published') ->assertDontSee('x-ref="canvas"', false); } private function publishAttempt(PublishStatusEnum $status, string $updatedAt): void { $routeArticle = RouteArticle::factory()->create(['publish_status' => $status]); // Model::update() would re-stamp updated_at to now(); bypass via the query builder. RouteArticle::query() ->whereKey($routeArticle->getKey()) ->update(['updated_at' => Carbon::parse($updatedAt)]); } public function test_it_renders_the_publish_success_rate_chart_on_mount(): void { Carbon::setTestNow('2026-07-15 13:45:00'); $this->publishAttempt(PublishStatusEnum::PUBLISHED, '2026-07-15 09:00:00'); Livewire::test(Dashboard::class) ->assertSee('Publish Success Rate') ->assertSee('trendChart(', false) ->assertSee('x-ref="canvas"', false); } public function test_it_refreshes_the_publish_success_rate_island_when_the_range_changes(): void { $this->publishAttempt(PublishStatusEnum::PUBLISHED, '2026-07-10 12:00:00'); $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('name=publish-success-rate|', $fragments); $this->assertStringContainsString('Publish Success Rate', $fragments); $this->assertStringContainsString('2026-07-10', $fragments); } public function test_it_charts_a_day_where_every_publish_failed(): void { $this->publishAttempt(PublishStatusEnum::ERROR, '2026-07-10 12:00:00'); $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringNotContainsString('No publish attempts in this range.', $fragments); } public function test_it_reports_a_range_with_no_publish_attempts(): void { $this->publishAttempt(PublishStatusEnum::SKIPPED, '2026-07-10 12:00:00'); $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); $this->assertStringContainsString('No publish attempts in this range.', $fragments); } public function test_it_lists_the_most_recent_activity(): void { ActivityLog::factory()->type(ActivityTypeEnum::PUBLISH)->loggedAt(Carbon::parse('2026-07-10 09:00:00'))->create(['message' => 'Older entry']); ActivityLog::factory()->type(ActivityTypeEnum::ERROR)->loggedAt(Carbon::parse('2026-07-12 09:00:00'))->create(['message' => 'Newer entry']); Livewire::test(Dashboard::class) ->assertSee('Recent Activity') ->assertSee('Newer entry') ->assertSee('Older entry') ->assertSeeInOrder(['Newer entry', 'Older entry']); } public function test_it_caps_the_activity_panel_at_five_entries(): void { foreach (range(1, 8) as $index) { ActivityLog::factory() ->loggedAt(Carbon::parse('2026-07-10 09:00:00')->addMinutes($index)) ->create(['message' => "Entry {$index}"]); } $component = Livewire::test(Dashboard::class); $component->assertSee('Entry 8')->assertSee('Entry 4'); $component->assertDontSee('Entry 3'); } public function test_the_activity_panel_ignores_the_global_range(): void { ActivityLog::factory() ->loggedAt(Carbon::parse('2026-07-10 09:00:00')) ->create(['message' => 'Activity outside the range']); Livewire::test(Dashboard::class) ->call('applyRange', '2026-08-01', '2026-08-31') ->assertSee('Activity outside the range'); } public function test_it_reports_an_empty_activity_panel(): void { Livewire::test(Dashboard::class)->assertSee('Nothing has happened yet.'); } public function test_it_links_the_activity_panel_to_the_activity_page(): void { Livewire::test(Dashboard::class)->assertSee(route('activity'), false); } public function test_it_shows_the_feed_name_for_an_article_subject(): void { $feed = Feed::factory()->create(['name' => 'Belga News']); $article = Article::factory()->create(['feed_id' => $feed->id]); ActivityLog::factory() ->forSubject($article) ->create(['message' => 'Fetched something']); Livewire::test(Dashboard::class) ->assertSee('Fetched something') ->assertSee('Belga News'); } public function test_it_renders_an_activity_entry_without_a_subject(): void { ActivityLog::factory()->create(['message' => 'Subjectless entry']); Livewire::test(Dashboard::class)->assertSee('Subjectless entry'); } public function test_it_renders_an_activity_entry_whose_subject_was_deleted(): void { $article = Article::factory()->create(); ActivityLog::factory()->forSubject($article)->create(['message' => 'Orphaned entry']); $article->delete(); Livewire::test(Dashboard::class)->assertSee('Orphaned entry'); } private function dashboardQueryCount(int $activityEntries): int { $feed = Feed::factory()->create(); foreach (range(1, $activityEntries) as $index) { $article = Article::factory()->create(['feed_id' => $feed->id]); ActivityLog::factory()->forSubject($article)->create(['message' => "Entry {$index}"]); } DB::flushQueryLog(); DB::enableQueryLog(); Livewire::test(Dashboard::class); $queries = count(DB::getQueryLog()); DB::disableQueryLog(); return $queries; } public function test_it_does_not_fan_out_queries_per_activity_entry(): void { $this->assertSame( $this->dashboardQueryCount(1), $this->dashboardQueryCount(20), ); } public function test_every_range_dependent_island_re_renders_on_a_range_change(): void { $fragments = $this->islandFragments( Livewire::test(Dashboard::class)->call('applyRange', '2026-07-01', '2026-07-31') ); foreach (['articles-trend', 'approval-rate', 'publish-success-rate', 'articles-per-feed', 'publications-per-channel'] as $island) { $this->assertStringContainsString("name={$island}|", $fragments); } } }