create(); /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); $article = Article::factory()->create(['feed_id' => $feed->id, 'title' => $title]); /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->forRoute($route)->create([ 'article_id' => $article->id, 'approval_status' => $status, 'validated_at' => now(), ]); return $routeArticle; } private function createRouteArticleForFeed(Feed $feed, string $title, ApprovalStatusEnum $status = ApprovalStatusEnum::PENDING): RouteArticle { /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); $article = Article::factory()->create(['feed_id' => $feed->id, 'title' => $title]); /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->forRoute($route)->create([ 'article_id' => $article->id, 'approval_status' => $status, 'validated_at' => now(), ]); return $routeArticle; } /** * @param Testable $component * @return array */ private function renderedFeedGroupIds(Testable $component): array { preg_match_all('/wire:key="feed-group-(\d+)"/', $component->html(), $matches); return array_map('intval', $matches[1]); } public function test_renders_successfully(): void { Livewire::test(Articles::class) ->assertStatus(200); } public function test_defaults_to_pending_tab(): void { Livewire::test(Articles::class) ->assertSet('tab', 'pending'); } public function test_pending_tab_shows_only_pending_route_articles(): void { $feed = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($feed, 'Pending Article'); $this->createRouteArticleForFeed($feed, 'Approved Article', ApprovalStatusEnum::APPROVED); $this->createRouteArticleForFeed($feed, 'Rejected Article', ApprovalStatusEnum::REJECTED); Livewire::test(Articles::class) ->call('toggleFeed', $feed->id) ->assertSee('Pending Article') ->assertDontSee('Approved Article') ->assertDontSee('Rejected Article'); } public function test_all_tab_shows_all_route_articles(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Pending Article'); $this->createRouteArticle(ApprovalStatusEnum::APPROVED, 'Approved Article'); $this->createRouteArticle(ApprovalStatusEnum::REJECTED, 'Rejected Article'); Livewire::test(Articles::class) ->call('setTab', 'all') ->assertSee('Pending Article') ->assertSee('Approved Article') ->assertSee('Rejected Article'); } public function test_all_tab_search_filters_by_title(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Belgian Politics Update'); $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Weather Forecast Today'); Livewire::test(Articles::class) ->call('setTab', 'all') ->set('search', 'Belgian') ->assertSee('Belgian Politics Update') ->assertDontSee('Weather Forecast Today'); } public function test_approve_changes_status_and_dispatches_event(): void { Event::fake([RouteArticleApproved::class]); $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING); Livewire::test(Articles::class) ->call('approve', $routeArticle->id); $this->assertEquals(ApprovalStatusEnum::APPROVED, $routeArticle->fresh()->approval_status); Event::assertDispatched(RouteArticleApproved::class); } public function test_reject_changes_status(): void { $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING); Livewire::test(Articles::class) ->call('reject', $routeArticle->id); $this->assertEquals(ApprovalStatusEnum::REJECTED, $routeArticle->fresh()->approval_status); } public function test_restore_changes_status_back_to_pending(): void { $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::REJECTED); Livewire::test(Articles::class) ->call('restore', $routeArticle->id); $this->assertEquals(ApprovalStatusEnum::PENDING, $routeArticle->fresh()->approval_status); } public function test_clear_rejects_all_pending_route_articles(): void { $pending1 = $this->createRouteArticle(ApprovalStatusEnum::PENDING); $pending2 = $this->createRouteArticle(ApprovalStatusEnum::PENDING); $approved = $this->createRouteArticle(ApprovalStatusEnum::APPROVED); Livewire::test(Articles::class) ->call('clear'); $this->assertEquals(ApprovalStatusEnum::REJECTED, $pending1->fresh()->approval_status); $this->assertEquals(ApprovalStatusEnum::REJECTED, $pending2->fresh()->approval_status); $this->assertEquals(ApprovalStatusEnum::APPROVED, $approved->fresh()->approval_status); } public function test_clear_only_rejects_the_filtered_feed(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $belgaArticle = $this->createRouteArticleForFeed($belga, 'Belga Headline'); Livewire::test(Articles::class) ->set('feedId', $vrt->id) ->call('clear'); $this->assertEquals(ApprovalStatusEnum::REJECTED, $vrtArticle->fresh()->approval_status); $this->assertEquals(ApprovalStatusEnum::PENDING, $belgaArticle->fresh()->approval_status); } public function test_clear_is_a_no_op_when_the_filtered_feed_has_nothing_pending(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $belgaApproved = $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED); Livewire::test(Articles::class) ->set('feedId', $belga->id) ->call('clear'); $this->assertEquals(ApprovalStatusEnum::PENDING, $vrtArticle->fresh()->approval_status); $this->assertEquals(ApprovalStatusEnum::APPROVED, $belgaApproved->fresh()->approval_status); } public function test_clear_confirmation_counts_only_the_filtered_feed(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT One'); $this->createRouteArticleForFeed($vrt, 'VRT Two'); $this->createRouteArticleForFeed($belga, 'Belga One'); Livewire::test(Articles::class) ->set('feedId', $vrt->id) ->assertSee('Reject 2 pending route articles from the selected feed?') ->set('feedId', null) ->assertSee('Reject 3 pending route articles?'); } public function test_clear_button_hidden_when_filtered_feed_has_nothing_pending(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED); Livewire::test(Articles::class) ->set('feedId', $belga->id) ->assertDontSee('Clear Feed') ->assertDontSee('Clear All'); } public function test_clear_button_labels_its_scope(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); Livewire::test(Articles::class) ->assertSee('Clear All') ->set('feedId', $vrt->id) ->assertSee('Clear Feed'); } public function test_pending_count_badge_shows_correct_count(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::APPROVED); Livewire::test(Articles::class) ->assertSeeInOrder(['Pending', '2']); } public function test_switching_tabs_resets_search(): void { Livewire::test(Articles::class) ->call('setTab', 'all') ->set('search', 'something') ->call('setTab', 'pending') ->assertSet('search', '') ->assertSet('tab', 'pending'); } public function test_shows_route_name_in_listing(): void { $feed = Feed::factory()->create(['name' => 'VRT News']); /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); $article = Article::factory()->create(['feed_id' => $feed->id, 'title' => 'Test']); RouteArticle::factory()->forRoute($route)->create([ 'article_id' => $article->id, 'approval_status' => ApprovalStatusEnum::PENDING, 'validated_at' => now(), ]); Livewire::test(Articles::class) ->assertSee('VRT News'); } public function test_pending_tab_lists_feeds_collapsed_without_articles(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Headline'); Livewire::test(Articles::class) ->assertSee('VRT News') ->assertSee('Belga Press') ->assertDontSee('VRT Headline') ->assertDontSee('Belga Headline'); } public function test_pending_feed_badge_shows_total_count_not_page_count(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); for ($i = 0; $i < 20; $i++) { $this->createRouteArticleForFeed($vrt, "VRT Article {$i}"); } $html = (string) preg_replace('/\s+/', ' ', Livewire::test(Articles::class)->html()); $this->assertStringContainsString('VRT News 20 ', $html); } public function test_expanding_a_feed_reveals_only_that_feeds_articles(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Headline'); Livewire::test(Articles::class) ->call('toggleFeed', $vrt->id) ->assertSee('VRT Headline') ->assertDontSee('Belga Headline'); } public function test_expanding_a_feed_shows_all_its_articles_beyond_one_page(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); for ($i = 0; $i < 20; $i++) { $this->travelTo(now()->addMinutes($i), function () use ($vrt, $i) { $this->createRouteArticleForFeed($vrt, "VRT Article {$i}"); }); } $component = Livewire::test(Articles::class)->call('toggleFeed', $vrt->id); for ($i = 0; $i < 20; $i++) { $component->assertSee("VRT Article {$i}"); } } public function test_toggling_an_expanded_feed_collapses_it(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); Livewire::test(Articles::class) ->call('toggleFeed', $vrt->id) ->assertSee('VRT Headline') ->call('toggleFeed', $vrt->id) ->assertDontSee('VRT Headline'); } public function test_switching_tabs_collapses_expanded_feeds(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); Livewire::test(Articles::class) ->call('toggleFeed', $vrt->id) ->call('setTab', 'all') ->call('setTab', 'pending') ->assertSet('expandedFeeds', []) ->assertDontSee('VRT Headline'); } public function test_all_tab_shows_a_flat_list_not_feed_groups(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Article'); $this->createRouteArticleForFeed($belga, 'Belga Article'); Livewire::test(Articles::class) ->call('setTab', 'all') ->assertViewHas('pendingFeeds', null) ->assertSee('VRT Article') ->assertSee('Belga Article'); } public function test_feed_filter_narrows_pending_tab_to_one_feed(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Headline'); $component = Livewire::test(Articles::class)->set('feedId', $vrt->id); $this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component)); } public function test_feed_filter_narrows_all_tab_to_one_feed(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Headline'); Livewire::test(Articles::class) ->call('setTab', 'all') ->set('feedId', $vrt->id) ->assertSee('VRT Headline') ->assertDontSee('Belga Headline'); } public function test_feed_filter_combines_with_search_on_all_tab(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'Belgian Politics Update'); $this->createRouteArticleForFeed($vrt, 'Weather Forecast Today'); $this->createRouteArticleForFeed($belga, 'Belgian Economy Report'); Livewire::test(Articles::class) ->call('setTab', 'all') ->set('feedId', $vrt->id) ->set('search', 'Belgian') ->assertSee('Belgian Politics Update') ->assertDontSee('Weather Forecast Today') ->assertDontSee('Belgian Economy Report'); } public function test_feed_filter_combines_with_tab_status_filtering(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($vrt, 'VRT Pending'); $this->createRouteArticleForFeed($vrt, 'VRT Approved', ApprovalStatusEnum::APPROVED); Livewire::test(Articles::class) ->set('feedId', $vrt->id) ->call('toggleFeed', $vrt->id) ->assertSee('VRT Pending') ->assertDontSee('VRT Approved'); } public function test_clearing_feed_filter_restores_all_feeds(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Headline'); $component = Livewire::test(Articles::class)->set('feedId', $vrt->id); $this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component)); $component->set('feedId', null); $this->assertEqualsCanonicalizing([$vrt->id, $belga->id], $this->renderedFeedGroupIds($component)); } public function test_changing_feed_filter_resets_pagination(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); for ($i = 0; $i < 20; $i++) { $this->createRouteArticleForFeed($vrt, "VRT Article {$i}"); } Livewire::test(Articles::class) ->call('setTab', 'all') ->set('paginators.page', 2) ->set('feedId', $vrt->id) ->assertSet('paginators.page', 1); } public function test_changing_feed_filter_collapses_expanded_feeds(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); Livewire::test(Articles::class) ->call('toggleFeed', $vrt->id) ->assertSee('VRT Headline') ->set('feedId', $vrt->id) ->assertSet('expandedFeeds', []) ->assertDontSee('VRT Headline'); } public function test_feed_filter_persists_across_tab_switches(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); Livewire::test(Articles::class) ->set('feedId', $vrt->id) ->call('setTab', 'all') ->assertSet('feedId', $vrt->id) ->call('setTab', 'pending') ->assertSet('feedId', $vrt->id); } public function test_feed_filter_options_only_include_feeds_with_route_articles(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $unused = Feed::factory()->create(['name' => 'Unused Feed']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); Livewire::test(Articles::class) ->assertSeeHtml('') ->assertDontSeeHtml(''); } public function test_empty_state_when_feed_filter_matches_nothing_on_pending_tab(): void { $vrt = Feed::factory()->create(['name' => 'VRT News']); $belga = Feed::factory()->create(['name' => 'Belga Press']); $this->createRouteArticleForFeed($vrt, 'VRT Headline'); $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED); Livewire::test(Articles::class) ->set('feedId', $belga->id) ->assertSee('No pending articles for the selected feed.'); } public function test_empty_state_on_pending_tab(): void { Livewire::test(Articles::class) ->assertSee('No pending articles'); } public function test_empty_state_on_all_tab(): void { Livewire::test(Articles::class) ->call('setTab', 'all') ->assertSee('No route articles have been created yet.'); } }